commands.ps1
function ConvertFrom-MarkdownLine { <# .SYNOPSIS Converts markdown notation of bold and cursive to html. .DESCRIPTION Converts markdown notation of bold and cursive to html. .PARAMETER Line The line of text to convert. .EXAMPLE PS C:\> Convert-MarkdownLine -Line '_value1_' Will convert "_value1_" to "<i>value1</i>" #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true)] [string[]] $Line ) process { foreach ($string in $Line) { $string -replace '\*\*(.+?)\*\*', '<b>$1</b>' -replace '_(.+?)_', '<i>$1</i>' } } } function ConvertFrom-MdBlock { <# .SYNOPSIS Converts special blocks defined in markdown into html. .DESCRIPTION Converts special blocks defined in markdown into html. The resultant html is appended to the stringbuilder specified. The conversion logic is provided by Register-EBMarkdownBlock. Returns whether the next line should be a first paragraph or a regular paragraph. .PARAMETER Type What kind of block is this? .PARAMETER Lines The lines of text contained in the block. .PARAMETER Attributes Any attributes provided to the block. .PARAMETER StringBuilder The stringbuilder containing the overall html string being built. .EXAMPLE PS C:\> ConvertFrom-MdBlock -Type $type -Lines $lines -Attributes @{ } -StringBuilder $builder Converts the provided block data to html and appends it to the stringbuilder. Returns whether the next line should be a first paragraph or a regular paragraph. #> [CmdletBinding()] param ( [parameter(Mandatory = $true)] [string] $Type, [parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $Lines, [parameter(Mandatory = $true)] [System.Collections.Hashtable] $Attributes, [parameter(Mandatory = $true)] [System.Text.StringBuilder] $StringBuilder ) process { $converter = $script:mdBlockTypes[$Type] if (-not $converter) { Stop-PSFFunction -Message "Converter for block $Type not found! Make sure it is properly registered using Register-EBMarkdownBlock" -EnableException $true -Cmdlet $PSCmdlet -Category InvalidArgument } $data = [pscustomobject]($PSBoundParameters | ConvertTo-PSFHashtable) $converter.Invoke($data) -as [bool] } } function ConvertTo-MarkdownLine { <# .SYNOPSIS Converts an input html paragraph to a markdown line of text. .DESCRIPTION Converts an input html paragraph to a markdown line of text. .PARAMETER Line The line of text to convert. .EXAMPLE PS C:\> ConvertTo-MarkdownLine -Line $Line Converts the HTML $Line to markdown #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true)] [AllowEmptyString()] [string[]] $Line ) begin { $mapping = @{ '</{0,1}em>' = '_' '</{0,1}i>' = '_' '</{0,1}strong>' = '**' '</{0,1}b>' = '**' '<br>' = '<br />' } } process { foreach ($string in $Line) { foreach ($pair in $mapping.GetEnumerator()) { $string = $string -replace $pair.Key, $pair.Value } ($string -replace '</{0,1}p.{0,}?>').Trim() } } } function Read-RRChapter { <# .SYNOPSIS Reads a Royal Road chapter and breaks it down into its components. .DESCRIPTION Reads a Royal Road chapter and breaks it down into its components. Part of the parsing process to convert Royal Road books into eBooks. .PARAMETER Url Url to the specific RR page to process. .PARAMETER Index The chapter index to include in the return object .EXAMPLE PS C:\> Read-RRChapter -Url https://www.royalroad.com/fiction/12345/evil-incarnate/chapter/666666/1-end-of-all-days Reads and converts the first chapter of evil incarnate (hint: does not exist) #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Url, [int] $Index ) begin { #region functions function Get-NextLink { [OutputType([string])] [CmdletBinding()] param ( [parameter(ValueFromPipeline = $true)] [string] $Line ) process { if ($Line -notlike '*<a class="btn btn-primary*>Next <br class="visible-xs" />Chapter</a>*') { return } $Line -replace '^.+href="(.+?)".+$', 'https://www.royalroad.com$1' } } function ConvertTo-Markdown { [OutputType([string])] [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] [string] $Line ) begin { $firstLineCompleted = $false $badQuotes = @( [char]8220 [char]8221 [char]8222 [char]8223 ) $badQuotesPattern = $badQuotes -join "|" $badSingleQuotes = @( [char]8216 [char]8217 [char]8218 [char]8219 ) $badSingleQuotesPattern = $badSingleQuotes -join "|" } process { $lineNormalized = ($Line -replace $badQuotesPattern, '"' -replace $badSingleQuotesPattern, "'").Trim() if (-not $firstLineCompleted) { '# {0}' -f ($lineNormalized -replace '</{0,1}p.{0,}?>' -replace '</{0,1}b>' -replace '</{0,1}strong>' -replace '<br>', '<br />') '' $firstLineCompleted = $true return } if ($lineNormalized -eq '<p style="text-align: center">* * *</p>') { @' ## <divide> * * * ## </divide> '@ return } $lineNormalized | ConvertTo-MarkdownLine '' } } #endregion functions } process { $found = $false $allLines = (Invoke-WebRequest -Uri $Url -UseBasicParsing).Content -split "`n" $lines = $allLines | Where-Object { if ($_ -like '*<div class="chapter-inner chapter-content">*') { $found = $true } if ($_ -like '*<h6 class="bold uppercase text-center">Advertisement</h6>*') { $found = $false } # Remove all pictures, they don't close the tags correctly if ( $_ -like '*<img*' -or $_ -like '*<input*' ) { return } $found } [pscustomobject]@{ Index = $Index RawText = $allLines -join "`n" Text = $lines -join "`n" -replace '<br>', '<br />' -replace '<div class="chapter-inner chapter-content">', '<div>' TextMD = $lines[1 .. ($lines.Length - 2)] | ConvertTo-Markdown | Join-String "`n" NextLink = $allLines | Get-NextLink } } } function Export-EBBook { <# .SYNOPSIS Exports pages and images into a epub ebook. .DESCRIPTION Exports pages and images into a epub ebook. .PARAMETER Path The path to export to. Will ignore the name if an explicit filename was specified. .PARAMETER Name The name of the ebook. Will also be used for the filename if a path to a folder was specified. .PARAMETER Author The author to set for the ebook. .PARAMETER Publisher The publisher of the ebook. .PARAMETER CssData Custom CSS to use to style the ebook. Allows you to tune how the ebook is styled. .PARAMETER Page The pages to compile into an ebook. .EXAMPLE PS C:\> Read-EBMicrosoftDocsIndexPage -Url https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/best-practices-for-securing-active-directory | Export-EBBook -Path . -Name ads-best-practices.epub -Author "Friedrich Weinmann" -Publisher "Infernal Press" Compiles an ebook out of the Active Directory Best Practices. #> [CmdletBinding()] param ( [PsfValidateScript({ Resolve-PSFPath -Path $args[0] -Provider FileSystem -SingleItem -NewChild }, ErrorMessage = "Folder to place the file in must exist!")] [string] $Path = ".", [string] $Name = "New Book", [string] $Author = $env:USERNAME, [string] $Publisher = $env:USERNAME, [string] $CssData, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [EbookBuilder.Item[]] $Page ) begin { function Write-File { [CmdletBinding()] param ( [System.IO.DirectoryInfo] $Root, [string] $Path, [string] $Text ) $tempPath = Resolve-PSFPath -Path (Join-Path $Root.FullName $Path) -NewChild Write-PSFMessage -Level SomewhatVerbose -Message "Writing file: $($Path)" $utf8NoBom = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($tempPath, $Text, $utf8NoBom) } function ConvertTo-ManifestPageData { [CmdletBinding()] param ( $Pages ) $lines = $Pages | ForEach-Object { ' <item id="{0}" href="Text/{0}" media-type="application/xhtml+xml"/>' -f $_.EbookFileName } $lines -join "`n" } function ConvertToManifestImageData { [CmdletBinding()] param ( $Images ) $lines = $images | ForEach-Object { ' <item id="{0}" href="Images/{1}" media-type="image/{2}"/>' -f $_.ImageID, $_.FileName, "Jpeg" } $lines -join "`n" } #region Prepare Resources $resolvedPath = Resolve-PSFPath -Path $Path -Provider FileSystem -SingleItem -NewChild if (Test-Path $resolvedPath) { if ((Get-Item $resolvedPath).PSIsContainer) { $resolvedPath = Join-Path $resolvedPath $Name } } if ($resolvedPath -notlike "*.epub") { $resolvedPath += ".epub" } $zipPath = $resolvedPath -replace 'epub$', 'zip' $cssContent = $CssData if (-not $cssContent) { $cssContent = [System.IO.File]::ReadAllText((Resolve-Path "$($script:ModuleRoot)\data\Common.css"), [System.Text.Encoding]::UTF8) } $pages = @() $images = @() #endregion Prepare Resources } process { #region Process Input items foreach ($item in $Page) { switch ($item.Type) { "Page" { $pages += $item } "Image" { $images += $item } } } #endregion Process Input items } end { $id = 1 $pages = $pages | Sort-Object Index | Select-PSFObject -KeepInputObject -Property @{ Name = "EbookFileName" Expression = { "{0}.xhtml" -f (New-Guid) } }, @{ Name = "TocIndex" Expression = { $id++ } } $tempPath = New-Item -Path $env:TEMP -Name "Ebook-$(Get-Random -Maximum 99999 -Minimum 10000)" -ItemType Directory -Force Write-File -Root $tempPath -Path 'mimetype' -Text 'application/epub+zip' $metaPath = New-Item -Path $tempPath.FullName -Name "META-INF" -ItemType Directory Write-File -Root $metaPath -Path 'cotnainer.xml' -Text @' <?xml version="1.0" encoding="UTF-8"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/> </rootfiles> </container> '@ $oebpsPath = New-Item -Path $tempPath.FullName -Name "OEBPS" -ItemType Directory #region content.opf $contentOpfText = @' <?xml version="1.0" encoding="utf-8"?> <package version="2.0" unique-identifier="uuid_id" xmlns="http://www.idpf.org/2007/opf"> <metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"> <dc:publisher>{0}</dc:publisher> <dc:language>en</dc:language> <dc:creator opf:role="aut" opf:file-as="{1}">{1}</dc:creator> <dc:title opf:file-as="{2}">{2}</dc:title> </metadata> <manifest> {3} {4} <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/> <item id="style.css" href="Styles/Style.css" media-type="text/css"/> </manifest> <spine toc="ncx"> {5} </spine> <guide/> </package> '@ -f $Publisher, $Author, $Name, (ConvertTo-ManifestPageData -Pages $pages), (ConvertToManifestImageData -Images $images), (($pages | ForEach-Object { ' <itemref idref="{0}"/>' -f $_.EbookFileName }) -join "`n") Write-File -Root $oebpsPath -Path 'content.opf' -Text $contentOpfText #endregion content.opf #region TOC.ncx $bookMarkText = ($pages | ForEach-Object { @' <navPoint id="navPoint-{0}" playOrder="{0}"> <navLabel> <text>Chapter {0}</text> </navLabel> <content src="Text/{1}"/> </navPoint> '@ -f $_.TocIndex, $_.EbookFileName }) -join "`n" $contentTocNcxText = @' <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"><ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/"> <head> <meta content="{0}" name="dtb:uid"/> <meta content="1" name="dtb:depth"/> <meta content="0" name="dtb:totalPageCount"/> <meta content="0" name="dtb:maxPageNumber"/> </head> <docTitle> <text>{1}</text> </docTitle> <navMap> {2} </navMap> </ncx> '@ -f (New-Guid), $Name, $bookMarkText Write-File -Root $oebpsPath -Path 'toc.ncx' -Text $contentTocNcxText #endregion TOC.ncx #region Files $stylesPath = New-Item -Path $oebpsPath.FullName -Name "Styles" -ItemType Directory Write-File -Root $stylesPath -Path 'Style.css' -Text $cssContent $textPath = New-Item -Path $oebpsPath.FullName -Name 'Text' -ItemType Directory foreach ($pageItem in $pages) { $pageText = @' <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>{0}</title> <meta content="http://www.w3.org/1999/xhtml; charset=utf-8" http-equiv="Content-Type"/> <link href="../Styles/Style.css" type="text/css" rel="stylesheet"/> </head> <body> {1} </body> </html> '@ -f $Name, $pageItem.Content Write-File -Root $textPath -Path $pageItem.EbookFileName -Text $pageText } #endregion Files #region Images if ($images) { $imagesPath = New-Item -Path $oebpsPath.FullName -Name 'Images' -ItemType Directory foreach ($image in $images) { $targetPath = Join-Path $imagesPath.FullName $image.FileName [System.IO.File]::WriteAllBytes($targetPath, $image.Data) } } #endregion Images Get-ChildItem $tempPath | Compress-Archive -DestinationPath $zipPath Rename-Item -Path $zipPath -NewName (Split-Path $resolvedPath -Leaf) Remove-Item $tempPath -Recurse -Force } } function Read-EBMarkdown { <# .SYNOPSIS Reads a markdown file and converts it to a page to be built into an ebook .DESCRIPTION Reads a markdown file and converts it to a page to be built into an ebook .PARAMETER Path Path to the file to read. .EXAMPLE PS C:\> Get-ChildItem *.md | Read-EBMarkdown Reads and converts all markdown files in he current folder #> [CmdletBinding()] param ( [parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('FullName')] [string[]] $Path ) begin { function ConvertFrom-Markdown { [CmdletBinding()] param ( [string] $Path, [int] $Index ) $lines = Get-Content -Path $Path -Encoding UTF8 $stringBuilder = [System.Text.StringBuilder]::new() $inBlock = $false $blockData = [pscustomobject]@{ Attributes = @{ } Type = $null Lines = @() } $paragraph = @() $firstPar = $true foreach ($line in $lines) { #region Process Block Content if ($inBlock) { if ($line -like '## <*') { try { $firstPar = ConvertFrom-MdBlock -Type $blockData.Type -Lines $blockData.Lines -Attributes $blockData.Attributes -StringBuilder $stringBuilder } catch { Stop-PSFFunction -Message 'Failed to convert block' -ErrorRecord $_ -Target $blockData -EnableException $true -Cmdlet $PSCmdlet } $inBlock = $false } else { $blockData.Lines += $line } continue } #endregion Process Block Content # Handle Chapter Title if ($line -like '# *') { $null = $stringBuilder.AppendLine("<h2>$line</h2>") continue } # Handle begin of a Block if ($line -like '## <*') { $inBlock = $true $blockData = New-Block -Line $line continue } #region Process paragraph if ($line.Trim() -eq "") { if (-not $paragraph) { continue } $class = 'text' if ($firstPar) { $class = 'firstpar' $firstPar = $false } $null = $stringBuilder.AppendLine("<p class=`"$class`">$(($paragraph -join " ") -replace '\*\*(.+?)\*\*','<b>$1</b>' -replace '_(.+?)_','<i>$1</i>')</p>") $paragraph = @() continue } $paragraph += $line #endregion Process paragraph } #region Ensure final paragraph is taken care of if ($paragraph) { $class = 'text' if ($firstPar) { $class = 'firstpar' $firstPar = $false } $null = $stringBuilder.AppendLine("<p class=`"$class`">$(($paragraph -join " ") -replace '\*\*(.+?)\*\*', '<b>$1</b>' -replace '_(.+?)_', '<i>$1</i>')</p>") } #endregion Ensure final paragraph is taken care of New-Object EbookBuilder.Page -Property @{ Index = $Index Name = (Get-Item -Path $Path).BaseName Content = $stringBuilder.ToString() SourceName = $Path TimeCreated = Get-Date MetaData = @{ } } } function New-Block { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [string] $Line ) $type = $Line -replace '## <(\w+).+$', '$1' $attributes = @{ } $entries = $Line | Select-String '(\w+)="(.+?)"' -AllMatches foreach ($match in $entries.Matches) { $attributes[$match.Groups[1].Value] = $match.Groups[2].Value } [pscustomobject]@{ Attributes = $attributes Type = $type Lines = @() } } $Index = 1 } process { foreach ($pathItem in $Path) { ConvertFrom-Markdown -Path $pathItem -Index $Index $Index++ } } } function Read-EBMicrosoftDocsIndexPage { <# .SYNOPSIS Converts an index page of a Microsoft Docs into a book. .DESCRIPTION Converts an index page of a Microsoft Docs into a book. Resolves all links in the index. .PARAMETER Url The Url to the index page. .PARAMETER StartIndex Start Index the pages will begin with. Index is what Export-EBBook will use to determine page order. .EXAMPLE PS C:\> Read-EBMicrosoftDocsIndexPage -Url https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/best-practices-for-securing-active-directory Parses the Active Directory Security Best Practices into page and image objects. #> [CmdletBinding()] Param ( [string] $Url, [int] $StartIndex = 0 ) begin { $index = $StartIndex } process { $indexPage = Read-EBMicrosoftDocsPage -Url $Url -StartIndex $index $indexPage $index++ $pages = $indexPage.Content | Select-String '<a href="(.*?)"' -AllMatches | Select-Object -ExpandProperty Matches | ForEach-Object { $_.Groups[1].Value } $basePath = (Split-Path $indexPage.SourceName) -replace "\\", "/" foreach ($page in $pages) { $tempPath = $basePath while ($page -like "../*") { $tempPath = (Split-Path $tempPath) -replace "\\", "/" $page = $page -replace "^../", "" } Read-EBMicrosoftDocsPage -Url ("{0}/{1}" -f $tempPath, $page) -StartIndex $index $index++ } } } function Read-EBMicrosoftDocsPage { <# .SYNOPSIS Parses a web document from the Microsoft documents. .DESCRIPTION Parses a web document from the Microsoft documents. .PARAMETER Url The url of the website to parse. .PARAMETER StartIndex The index of the page. Used for sorting the pages when building the ebook. .EXAMPLE PS C:\> Read-EBMicrosoftDocsPage -Url https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/best-practices-for-securing-active-directory Parses the file of the specified link and converts it into a page. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string[]] $Url, [int] $StartIndex = 1 ) begin { $index = $StartIndex } process { foreach ($weblink in $Url) { $data = Invoke-WebRequest -UseBasicParsing -Uri $weblink $main = ($data.RawContent | Select-String "(?ms)<main.*?>(.*?)</main>").Matches.Groups[1].Value $source, $title = ($main | Select-String '<h1.*?sourceFile="(.*?)".*?>(.*?)</h1>').Matches.Groups[1 .. 2].Value $text = ($main | Select-String '(?ms)<!-- <content> -->(.*?)<!-- </content> -->').Matches.Groups[1].Value.Trim() $content = "<h1>{0}</h1> {1}" -f $title, $text $webClient = New-Object System.Net.WebClient foreach ($imageMatch in ($content | Select-String '(<img.*?src="(.*?)".*?alt="(.*?)".*?>)' -AllMatches).Matches) { $relativeImagePath = $imageMatch.Groups[2].Value $imageName = $imageMatch.Groups[3].Value $imagePath = "{0}/{1}" -f ($weblink -replace '/[^/]*?$', '/'), $relativeImagePath $image = New-Object EbookBuilder.Image -Property @{ Data = $webClient.DownloadData($imagePath) Name = $imageName TimeCreated = Get-Date Extension = $imagePath.Split(".")[-1] MetaData = @{ WebLink = $imagePath } } $image $content = $content -replace ([regex]::Escape($relativeImagePath)), "../Images/$($image.FileName)" } New-Object EbookBuilder.Page -Property @{ Index = $index++ Name = $title Content = $content SourceName = $weblink TimeCreated = Get-Date MetaData = @{ GithubPath = $source } } } } } function Read-EBRoyalRoad { <# .SYNOPSIS Reads an entire series from Royal Road. .DESCRIPTION Reads an entire series from Royal Road. Converts it into the markdown format expected by Read-EBMarkdown. .PARAMETER Url The Url to the first chapter of a given Royal Road series .PARAMETER Name Name of the series .PARAMETER Books A hashtable mapping page numbers as the start of a book to the name of that book. If left empty, there will only be one book, named for the series. Each page number key must an integer type. .PARAMETER OutPath The folder in which to create one subfolder per book, in which the chapter files will be created. .EXAMPLE PS C:\> Read-EBRoyalRoad -Url https://www.royalroad.com/fiction/12345/evil-incarnate/chapter/666666/1-end-of-all-days -Name 'Evil Incarnate' -OutPath . Downloads the specified series, creates a folder in the current path and writes each chapter as its own .md file into that folder. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Url, [Parameter(Mandatory = $true)] [string] $Name, [hashtable] $Books = @{ }, [string] $OutPath ) begin { $index = 1 $bookCount = 1 if (-not $Books[1]) { $Books[1] = $Name } $currentBook = '{0} {1} - {2}' -f $Name, $bookCount, $Books[1] $currentBookPath = Join-Path -Path $OutPath -ChildPath $currentBook if (-not (Test-Path -Path $currentBookPath)) { $null = New-Item -Path $currentBookPath -Force -ItemType Directory -ErrorAction Stop } } process { $nextLink = $Url while ($nextLink) { Write-PSFMessage -Message 'Processing {0} Chapter {1} : {2}' -StringValues $Name, $index, $nextLink $page = Read-RRChapter -Url $nextLink -Index $index $index++ $nextLink = $page.NextLink $page.TextMD | Set-Content -Path ("{0}\{1}-{2:D4}-{3:D4}.md" -f $currentBookPath, $Name, $bookCount, $index) -Encoding UTF8 if ($Books[$index]) { $bookCount++ $currentBook = '{0} {1} - {2}' -f $Name, $bookCount, $Books[$index] $currentBookPath = Join-Path -Path $OutPath -ChildPath $currentBook if (-not (Test-Path -Path $currentBookPath)) { $null = New-Item -Path $currentBookPath -Force -ItemType Directory -ErrorAction Stop } } } } } function Register-EBMarkdownBlock { <# .SYNOPSIS Register a converter scriptblock for parsing block data with Read-EBMarkdown .DESCRIPTION Register a converter scriptblock for parsing block data with Read-EBMarkdown These allow you to custom-tailor and extend how special blocks are converted from markdown to html. The converter script receives one input object, which will contain three properties: - Type : What kind of block is being provided - Lines : The lines of text within the block - Attributes : Any attributes provided to the block - StringBuilder : The StringBuilder that you should append any lines of html to Your scriptblock should return a boolean value - whether the next paragraph should have the default indentation or be treated as a first line. .PARAMETER Name Name of the block. Equal to the html tag name used within markdown. .PARAMETER Converter Script logic performing the conversion. .EXAMPLE PS C:\> Register-EBMarkdownBlock -Name Warning -Converter $warningScript Registers a converter that will convert warning blocks to useful html. #> [CmdletBinding()] param ( [parameter(Mandatory = $true)] [string] $Name, [parameter(Mandatory = $true)] [System.Management.Automation.ScriptBlock] $Converter ) process { $script:mdBlockTypes[$Name] = $Converter } } |