XurrentHelpers.psm1
|
#Region './Private/Get-Section.ps1' -1 function Get-Section { <# .SYNOPSIS Extracts the body of a named Markdown H2 section. .DESCRIPTION Returns the trimmed text body of a ## Heading section from a Markdown string. Matches content from the heading line to the next same-level ## heading or end of string. Returns an empty string when the requested section is not found. .PARAMETER Content The full Markdown document content as a string. .PARAMETER Heading The section heading to search for, without the leading ## prefix. .EXAMPLE Get-Section -Content $markdown -Heading 'Description' #> [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [string] $Content, [Parameter(Mandatory = $true)] [string] $Heading ) process { $pattern = "(?ms)^##\s+$([regex]::Escape($Heading))\s*$\r?\n(.*?)(?=\r?\n^##\s+|\z)" if ($Content -match $pattern) { return $Matches[1].Trim() } return '' } } #EndRegion './Private/Get-Section.ps1' 44 #Region './Public/ConvertFrom-XurrentKnowledgeArticle.ps1' -1 function ConvertFrom-XurrentKnowledgeArticle { <# .SYNOPSIS Converts a Xurrent knowledge article CSV row to a Markdown file. .DESCRIPTION Takes a PSCustomObject in the Xurrent / 4me bulk-import CSV schema (as produced by Import-Csv or ConvertTo-XurrentKnowledgeArticle) and writes a *KnowledgeArticle.md file with the correct structure: H1 for Subject, **Keywords:** line, and ## Description and ## Instructions sections. Accepts pipeline input for batch processing of CSV rows. Invalid filename characters in Subject are replaced with hyphens. .PARAMETER InputObject A PSCustomObject with Subject, Description, Instructions, and Keywords properties, matching the Xurrent knowledge article CSV schema. Accepts pipeline input. .PARAMETER Path The folder in which to write the Markdown file. Defaults to the current working directory. .EXAMPLE Import-Csv .\knowledge_articles.csv | ConvertFrom-XurrentKnowledgeArticle -Path .\Articles .EXAMPLE $row = [PSCustomObject]@{ Subject = 'VPN Setup'; Description = 'How to...'; Instructions = 'Step 1...'; Keywords = 'vpn' } ConvertFrom-XurrentKnowledgeArticle -InputObject $row -Path .\Output #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSCustomObject] $InputObject, [Parameter()] [ValidateScript({ Test-Path $_ -PathType Container })] [string] $Path = (Get-Location).Path ) process { $subject = if ($InputObject.Subject) { [string]$InputObject.Subject } else { '' } $description = if ($InputObject.Description) { [string]$InputObject.Description } else { '' } $instructions = if ($InputObject.Instructions) { [string]$InputObject.Instructions } else { '' } $keywords = if ($InputObject.Keywords) { [string]$InputObject.Keywords } else { '' } $invalidChars = [System.IO.Path]::GetInvalidFileNameChars() -join '' $safeName = $subject -replace "[$([regex]::Escape($invalidChars))]", '-' $safeName = $safeName.Trim('-', ' ') if (-not $safeName) { $safeName = 'Unknown' } $fileName = '{0}KnowledgeArticle.md' -f $safeName $filePath = Join-Path -Path $Path -ChildPath $fileName $content = @" # $subject **Keywords:** $keywords ## Description $description ## Instructions $instructions "@ if ($PSCmdlet.ShouldProcess($filePath, 'Create knowledge article Markdown file')) { Set-Content -Path $filePath -Value $content -Encoding UTF8 Get-Item -Path $filePath } } } #EndRegion './Public/ConvertFrom-XurrentKnowledgeArticle.ps1' 79 #Region './Public/ConvertTo-XurrentKnowledgeArticle.ps1' -1 function ConvertTo-XurrentKnowledgeArticle { <# .SYNOPSIS Converts a Markdown knowledge article file to a Xurrent import object. .DESCRIPTION Reads a *KnowledgeArticle.md file and extracts the Subject (first H1 heading), Description (## Description section), Instructions (## Instructions section), and Keywords (**Keywords:** line) into a PSCustomObject formatted for the Xurrent / 4me bulk-import CSV schema. .PARAMETER File The input file to convert. Supports FileInfo, path strings, and objects that expose a FullName or Path property. Accepts pipeline input. .PARAMETER Service The Xurrent service name to write to the Service column of the import row. .PARAMETER ServiceInstances The Xurrent service instance name(s) for the Service Instances column. .EXAMPLE Get-Item .\MyAppKnowledgeArticle.md | ConvertTo-XurrentKnowledgeArticle -Service 'techwork automator' -ServiceInstances 'techwork automator for ACS' .EXAMPLE Get-ChildItem -Recurse -Filter '*KnowledgeArticle.md' | ConvertTo-XurrentKnowledgeArticle -Service 'my svc' -ServiceInstances 'my inst' #> [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('FullName', 'Path', 'PSPath')] [object] $File, [Parameter(Mandatory = $true)] [string] $Service, [Parameter(Mandatory = $true)] [string] $ServiceInstances ) process { $filePath = $null if ($File -is [System.IO.FileInfo]) { $filePath = $File.FullName } elseif ($File -is [string]) { $filePath = $File } elseif ($null -ne $File.PSObject.Properties['FullName']) { $filePath = [string]$File.FullName } elseif ($null -ne $File.PSObject.Properties['Path']) { $filePath = [string]$File.Path } if (-not $filePath) { throw 'File must be a path string, FileInfo, or an object with FullName/Path.' } $resolvedFile = Get-Item -LiteralPath $filePath -ErrorAction Stop if ($resolvedFile -isnot [System.IO.FileInfo]) { throw "Input path '$filePath' is not a file." } $raw = Get-Content -Path $resolvedFile.FullName -Raw -Encoding UTF8 $subject = '' if ($raw -match '(?m)^#\s+(.+)$') { $subject = $Matches[1].Trim() } $keywords = '' if ($raw -match '\*\*Keywords:\*\*\s*(.+)') { $keywords = $Matches[1].Trim() } [PSCustomObject]@{ ID = '' Source = '4me' 'Source ID' = '' Status = 'work_in_progress' Service = $Service 'Service Instances' = $ServiceInstances Subject = $subject Description = Get-Section -Content $raw -Heading 'Description' Instructions = Get-Section -Content $raw -Heading 'Instructions' Keywords = $keywords Template = '' } } } #EndRegion './Public/ConvertTo-XurrentKnowledgeArticle.ps1' 101 #Region './Public/Export-XurrentKnowledgeArticle.ps1' -1 function Export-XurrentKnowledgeArticle { <# .SYNOPSIS Exports Xurrent knowledge articles from Markdown files or objects to a CSV import file. .DESCRIPTION Accepts either a folder path (scanned recursively for *KnowledgeArticle.md files) or an array of pre-built article objects (e.g. the output of ConvertTo-XurrentKnowledgeArticle) and writes a CSV in the Xurrent / 4me bulk-import format. When using the Folder parameter set, Subject is read from the first H1 heading, Description and Instructions from same-named ## sections, Keywords from a **Keywords:** line. Service and ServiceInstances can be set via parameters or loaded from a .env file (SERVICE and SERVICE_INSTANCES keys). When using the InputObject parameter set the objects are exported as-is; Service, ServiceInstances, and EnvFile parameters are not applicable. .PARAMETER Folder Path to the folder to scan recursively for *KnowledgeArticle.md files. .PARAMETER InputObject One or more knowledge article objects (as returned by ConvertTo-XurrentKnowledgeArticle) to export directly without scanning a folder. Accepts pipeline input. .PARAMETER Service Xurrent service name written to the Service column of every export row. Defaults to 'techwork automator'. Can also be supplied via SERVICE= in a .env file. .PARAMETER ServiceInstances Xurrent service instance name(s) for the Service Instances column. Can be supplied via SERVICE_INSTANCES= in a .env file; prompts interactively if still empty. .PARAMETER OutputPath Full path of the CSV file to write. Defaults to import-knowledge_articles.csv in the current working directory. .PARAMETER EnvFile Path to a .env file supplying SERVICE and SERVICE_INSTANCES defaults. Defaults to .env in the current working directory when that file exists. .EXAMPLE Export-XurrentKnowledgeArticle -Folder .\ACS -Service 'techwork automator' -ServiceInstances 'techwork automator for ACS' .EXAMPLE Export-XurrentKnowledgeArticle -Folder .\TTTech Loads SERVICE and SERVICE_INSTANCES from .env in the current directory. .EXAMPLE Get-ChildItem -Recurse -Filter '*KnowledgeArticle.md' | ConvertTo-XurrentKnowledgeArticle -Service 'techwork automator' -ServiceInstances 'ACS' | Export-XurrentKnowledgeArticle -OutputPath .\out.csv Pipes pre-converted article objects directly into the export. .EXAMPLE $articles = ConvertTo-XurrentKnowledgeArticle -File .\MyArticleKnowledgeArticle.md -Service 'svc' -ServiceInstances 'inst' Export-XurrentKnowledgeArticle -InputObject $articles Passes an object array directly without pipeline. #> [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'FromFolder')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'FromFolder')] [ValidateScript({ Test-Path $_ -PathType Container })] [string] $Folder, [Parameter(Mandatory = $true, ParameterSetName = 'FromObjects', ValueFromPipeline = $true)] [object[]] $InputObject, [Parameter(ParameterSetName = 'FromFolder')] [string] $Service = 'techwork automator', [Parameter(ParameterSetName = 'FromFolder')] [string] $ServiceInstances = '', [Parameter()] [string] $OutputPath = (Join-Path (Get-Location).Path 'import-knowledge_articles.csv'), [Parameter(ParameterSetName = 'FromFolder')] [string] $EnvFile = (Join-Path (Get-Location).Path '.env') ) begin { $rows = [System.Collections.Generic.List[object]]::new() } process { if ($PSCmdlet.ParameterSetName -eq 'FromObjects') { foreach ($obj in $InputObject) { $rows.Add($obj) } return } if (Test-Path $EnvFile) { foreach ($line in (Get-Content $EnvFile)) { if ($line -match "^([A-Z_]+)\s*=\s*'?([^']*?)'?\s*$") { $key = $Matches[1] $val = $Matches[2] if ($key -eq 'SERVICE' -and -not $PSBoundParameters.ContainsKey('Service')) { $Service = $val } if ($key -eq 'SERVICE_INSTANCES' -and -not $PSBoundParameters.ContainsKey('ServiceInstances')) { $ServiceInstances = $val } } } } if (-not $Service) { $Service = Read-Host -Prompt "Enter SERVICE name (e.g. 'techwork automator')" } if (-not $ServiceInstances) { $ServiceInstances = Read-Host -Prompt 'Enter SERVICE_INSTANCES' } $files = Get-ChildItem -Path $Folder -Recurse -Filter '*KnowledgeArticle.md' if ($files.Count -eq 0) { Write-Warning "No *KnowledgeArticle.md files found in $Folder" return } Write-Verbose "Found $($files.Count) knowledge article(s) in $Folder" foreach ($file in $files) { Write-Verbose " Processing $($file.FullName)" $rows.Add((ConvertTo-XurrentKnowledgeArticle -File $file -Service $Service -ServiceInstances $ServiceInstances)) } } end { if ($rows.Count -eq 0) { return } if ($PSCmdlet.ShouldProcess($OutputPath, 'Export CSV')) { $encoding = if ($PSVersionTable.PSVersion.Major -ge 7) { 'utf8BOM' } else { 'UTF8' } $rows | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding $encoding Write-Verbose "Exported $($rows.Count) article(s) to $OutputPath" } } } #EndRegion './Public/Export-XurrentKnowledgeArticle.ps1' 155 #Region './Public/Import-XurrentKnowledgeArticle.ps1' -1 function Import-XurrentKnowledgeArticle { <# .SYNOPSIS Imports Xurrent knowledge articles from a CSV export file and writes Markdown files. .DESCRIPTION Reads a CSV file in the Xurrent / 4me bulk-import format and writes one *KnowledgeArticle.md file per row to the specified output folder. Subject becomes the H1 heading and the filename prefix, Description and Instructions populate their respective ## sections, and Keywords are written to a **Keywords:** line. Passes -WhatIf through to ConvertFrom-XurrentKnowledgeArticle for dry-run support. .PARAMETER CsvPath Path to the Xurrent knowledge article CSV export file to read. .PARAMETER OutputFolder The folder in which to write the Markdown files. Defaults to the current working directory. .EXAMPLE Import-XurrentKnowledgeArticle -CsvPath .\export-knowledge_articles.csv -OutputFolder .\Articles .EXAMPLE Import-XurrentKnowledgeArticle -CsvPath .\export.csv Writes Markdown files to the current working directory. #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string] $CsvPath, [Parameter()] [ValidateScript({ Test-Path $_ -PathType Container })] [string] $OutputFolder = (Get-Location).Path ) process { $rows = @(Import-Csv -Path $CsvPath -Encoding UTF8) if ($rows.Count -eq 0) { Write-Warning "No rows found in $CsvPath" return } Write-Verbose "Found $($rows.Count) row(s) in $CsvPath" if ($PSCmdlet.ShouldProcess($CsvPath, 'Import knowledge articles to Markdown')) { foreach ($row in $rows) { Write-Verbose " Processing '$($row.Subject)'" ConvertFrom-XurrentKnowledgeArticle -InputObject $row -Path $OutputFolder } } } } #EndRegion './Public/Import-XurrentKnowledgeArticle.ps1' 65 #Region './Public/New-XurrentKnowledgeArticleCsvExample.ps1' -1 function New-XurrentKnowledgeArticleCsvExample { <# .SYNOPSIS Creates an example CSV file in the Xurrent knowledge article import format. .DESCRIPTION Writes a CSV file with all columns required by the Xurrent / 4me bulk-import schema for knowledge articles, pre-filled with one illustrative example row. Use it as a reference or starting point before populating real data via Export-KnowledgeArticle. .PARAMETER OutputPath Full path of the CSV file to write. Defaults to example-knowledge_articles.csv in the current working directory. .EXAMPLE New-KnowledgeArticleCsvExample Creates example-knowledge_articles.csv in the current directory. .EXAMPLE New-KnowledgeArticleCsvExample -OutputPath C:\Temp\example.csv Creates the example CSV at the specified path. #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.IO.FileInfo])] param ( [Parameter()] [string] $OutputPath = (Join-Path (Get-Location).Path 'example-knowledge_articles.csv') ) process { $example = [PSCustomObject]@{ ID = '' Source = '4me' 'Source ID' = '' Status = 'work_in_progress' Service = 'my service' 'Service Instances' = 'my service instance' Subject = 'Example knowledge article subject' Description = 'A brief description of the knowledge article.' Instructions = 'Step 1: Do this. Step 2: Do that.' Keywords = 'example, keyword1, keyword2' Template = '' } if ($PSCmdlet.ShouldProcess($OutputPath, 'Create example knowledge article CSV')) { $encoding = if ($PSVersionTable.PSVersion.Major -ge 7) { 'utf8BOM' } else { 'UTF8' } $example | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding $encoding Get-Item -Path $OutputPath } } } #EndRegion './Public/New-XurrentKnowledgeArticleCsvExample.ps1' 57 #Region './Public/New-XurrentKnowledgeArticleTemplate.ps1' -1 function New-XurrentKnowledgeArticleTemplate { <# .SYNOPSIS Creates a Markdown template file for a Xurrent knowledge article. .DESCRIPTION Writes a *KnowledgeArticle.md template to the specified folder with the correct structure expected by ConvertTo-KnowledgeArticle: an H1 heading for the Subject, a **Keywords:** line, and ## Description and ## Instructions sections. .PARAMETER Name The name of the knowledge article, used as the H1 heading and filename prefix. The file is written as <Name>KnowledgeArticle.md. .PARAMETER Path The folder in which to create the template file. Defaults to the current working directory. .EXAMPLE New-KnowledgeArticleTemplate -Name 'HowToResetPassword' Creates HowToResetPasswordKnowledgeArticle.md in the current directory. .EXAMPLE New-KnowledgeArticleTemplate -Name 'VPN Setup' -Path C:\Articles Creates 'VPN SetupKnowledgeArticle.md' in C:\Articles. #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter()] [ValidateScript({ Test-Path $_ -PathType Container })] [string] $Path = (Get-Location).Path ) process { $fileName = '{0}KnowledgeArticle.md' -f $Name $filePath = Join-Path -Path $Path -ChildPath $fileName $template = @" # $Name **Keywords:** keyword1, keyword2 ## Description Describe the knowledge article here. ## Instructions Provide step-by-step instructions here. "@ if ($PSCmdlet.ShouldProcess($filePath, 'Create knowledge article template')) { Set-Content -Path $filePath -Value $template -Encoding UTF8 Get-Item -Path $filePath } } } #EndRegion './Public/New-XurrentKnowledgeArticleTemplate.ps1' 68 |