PSPublishModule.psm1
function Add-Directory { [CmdletBinding()] param($dir) $exists = Test-Path -Path $dir if ($exists -eq $false) {$createdDirectory = mkdir $dir} else {} } function Add-FilesWithFolders { [CmdletBinding()] param ($file, $FullProjectPath, $directory) $LinkPrivatePublicFiles = @() $path = $file foreach ($dir in $directory) { if ($path -like "$dir*") { $LinkPrivatePublicFiles += $path Write-Verbose "Adding file to linking list of files $path" } } return $LinkPrivatePublicFiles } function Add-FilesWithFoldersNew { [CmdletBinding()] param($File, $FullProjectPath, $directory) } function Add-ObjectTo { [CmdletBinding()] param($Object, $Type) Write-Verbose "Adding $($Object) to $Type" return $Object } function Copy-File { [CmdletBinding()] param ($Source, $Destination) if ((Test-Path $Source) -and !(Test-Path $Destination)) {Copy-Item -Path $Source -Destination $Destination} } function Export-PSData { [cmdletbinding()] <# .Synopsis Exports property bags into a data file .Description Exports property bags and the first level of any other object into a ps data file (.psd1) .Link https://github.com/StartAutomating/Pipeworks Import-PSData .Example Get-Web -Url http://www.youtube.com/watch?v=xPRC3EDR_GU -AsMicrodata -ItemType http://schema.org/VideoObject | Export-PSData .\PipeworksQuickstart.video.psd1 #> [OutputType([IO.FileInfo])] param([Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject[]] $InputObject, [Parameter(Mandatory = $true, Position = 0)] [string] $DataFile) begin {$AllObjects = New-Object Collections.ArrayList} process {$null = $AllObjects.AddRange($InputObject)} end { $text = $AllObjects | Write-PowerShellHashtable $text | Set-Content -Path $DataFile Get-Item -Path $DataFile } } function Find-EnumsList { [CmdletBinding()] param ([string] $ProjectPath) if ($PSEdition -eq 'Core') {$Enums = Get-ChildItem -Path $ProjectPath\Enums\*.ps1 -ErrorAction SilentlyContinue -FollowSymlink} else {$Enums = Get-ChildItem -Path $ProjectPath\Enums\*.ps1 -ErrorAction SilentlyContinue} $Opening = '@(' $Closing = ')' $Adding = ',' $EnumsList = New-ArrayList Add-ToArray -List $EnumsList -Element $Opening Foreach ($import in @($Enums)) { $Entry = "'Enums\$($import.Name)'" Add-ToArray -List $EnumsList -Element $Entry Add-ToArray -List $EnumsList -Element $Adding } Remove-FromArray -List $EnumsList -LastElement Add-ToArray -List $EnumsList -Element $Closing return [string] $EnumsList } function Format-Code { [cmdletbinding()] param([string] $FilePath, $FormatCode) if ($FormatCode.Enabled) { if ($FormatCode.RemoveComments) { Write-Verbose "Removing Comments - $FilePath" $Output = Remove-Comments -FilePath $FilePath } else {$Output = Get-Content -LiteralPath $FilePath -Raw} if ($null -eq $FormatCode.FormatterSettings) {$FormatCode.FormatterSettings = $Script:FormatterSettings} Write-Verbose "Formatting - $FilePath" $Output = Invoke-Formatter -ScriptDefinition $Output -Settings $FormatCode.FormatterSettings -Verbose:$false $Output = foreach ($O in $Output) {if ($O.Trim() -ne '') {$O.Trim()}} $Output | Out-File -LiteralPath $FilePath -NoNewline -Encoding utf8 } } function Format-PSD1 { [cmdletbinding()] param([string] $PSD1FilePath, $FormatCode) if ($FormatCode.Enabled) { $Output = Get-Content -LiteralPath $PSD1FilePath -Raw if ($FormatCode.RemoveComments) { Write-Verbose "Removing Comments - $PSD1FilePath" $Output = Remove-Comments -ScriptContent $Output } Write-Verbose "Formatting - $PSD1FilePath" if ($null -eq $FormatCode.FormatterSettings) {$FormatCode.FormatterSettings = $Script:FormatterSettings} $Output = Invoke-Formatter -ScriptDefinition $Output -Settings $FormatCode.FormatterSettings $Output | Out-File -LiteralPath $PSD1FilePath -NoNewline } } function Format-UsingNamespace { [CmdletBinding()] param([string] $FilePath, [string] $FilePathSave, [string] $FilePathUsing) if ($FilePathSave -eq '') {$FilePathSave = $FilePath} $FileStream = New-Object -TypeName IO.FileStream -ArgumentList ($FilePath), ([System.IO.FileMode]::Open), ([System.IO.FileAccess]::Read), ([System.IO.FileShare]::ReadWrite) $ReadFile = New-Object -TypeName System.IO.StreamReader -ArgumentList ($FileStream, [System.Text.Encoding]::UTF8, $true) $UsingNamespaces = [System.Collections.Generic.List[string]]::new() $AddTypes = [System.Collections.Generic.List[string]]::new() $Content = while (!$ReadFile.EndOfStream) { $Line = $ReadFile.ReadLine() if ($Line -like 'using namespace*') {$UsingNamespaces.Add($Line)} else {$Line} } $ReadFile.Close() $null = New-Item -Path $FilePathSave -ItemType file -Force if ($UsingNamespaces) { $null = New-Item -Path $FilePathUsing -ItemType file -Force $UsingNamespaces = $UsingNamespaces.Trim() | Sort-Object -Unique $UsingNamespaces | Add-Content -LiteralPath $FilePathUsing -Encoding utf8 $Content | Add-Content -LiteralPath $FilePathUsing -Encoding utf8 return $true } else { $Content | Add-Content -LiteralPath $FilePathSave -Encoding utf8 return $False } } Function Get-AliasTarget { [cmdletbinding()] param ([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('PSPath', 'FullName')] [string[]]$Path) process { foreach ($File in $Path) { $FileAst = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$null, [ref]$null) $FunctionName = $FileAst.FindAll( {param ($ast) $ast -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $true).Name $AliasDefinitions = $FileAst.FindAll( {param ($ast) $ast -is [System.Management.Automation.Language.StringConstantExpressionAst] -And $ast.Value -match '(New|Set)-Alias'}, $true) $AliasTarget = $AliasDefinitions.Parent.CommandElements.Where( {$_.StringConstantType -eq 'BareWord' -and $_.Value -notin ('New-Alias', 'Set-Alias', $FunctionName)}).Value $Attributes = $FileAst.FindAll( {param ($ast) $ast -is [System.Management.Automation.Language.AttributeAst]}, $true) $AliasDefinitions = $Attributes.Where( {$_.TypeName.Name -eq 'Alias' -and $_.Parent -is [System.Management.Automation.Language.ParamBlockAst]}) $AliasTarget += $AliasDefinitions.PositionalArguments.Value [PsCustomObject]@{Function = $FunctionName Alias = $AliasTarget } } } } function Get-FunctionAliases { [cmdletbinding()] param([string] $Path) Import-Module $Path -Force -Verbose:$False $Names = Get-FunctionNames -Path $Path $Aliases = foreach ($Name in $Names) {Get-Alias | Where-Object {$_.Definition -eq $Name}} return $Aliases } function Get-FunctionAliasesFromFolder { [cmdletbinding()] param([string] $FullProjectPath, [string[]] $Folder) foreach ($F in $Folder) { $Path = [IO.Path]::Combine($FullProjectPath, $F) if ($PSEdition -eq 'Core') {$Files = Get-ChildItem -Path $Path -File -Recurse -FollowSymlink} else {$Files = Get-ChildItem -Path $Path -File -Recurse} $AliasesToExport = foreach ($file in $Files) {Get-AliasTarget -Path $File.FullName | Select-Object -ExpandProperty Alias} $AliasesToExport } } function Get-FunctionNames { [cmdletbinding()] param([string] $Path, [switch] $Recurse) [Management.Automation.Language.Parser]::ParseFile((Resolve-Path $Path), [ref]$null, [ref]$null).FindAll( {param($c)$c -is [Management.Automation.Language.FunctionDefinitionAst]}, $Recurse).Name } function Get-FunctionNamesFromFolder { [cmdletbinding()] param([string] $FullProjectPath, [string[]] $Folder) foreach ($F in $Folder) { $Path = [IO.Path]::Combine($FullProjectPath, $F) if ($PSEdition -eq 'Core') {$Files = Get-ChildItem -Path $Path -File -Recurse -FollowSymlink} else {$Files = Get-ChildItem -Path $Path -File -Recurse} $FunctionToExport = foreach ($file in $Files) {Get-FunctionNames -Path $File.FullName} $FunctionToExport } } Function Get-ScriptComments { <# .Synopsis Get comments from a PowerShell script file. .Description This command will use the AST parser to go through a PowerShell script, either a .ps1 or .psm1 file, and display only the comments. .Example PS C:\> get-scriptcomments c:\scripts\MyScript.ps1 #> [cmdletbinding()] Param([Parameter(Position = 0, Mandatory, HelpMessage = "Enter the path of a PS1 file", ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("PSPath", "Name")] [ValidateScript( {Test-Path $_})] [ValidatePattern("\.ps(1|m1)$")] [string]$Path) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" New-Variable astTokens -force New-Variable astErr -force } Process { $Path = Convert-Path -Path $Path Write-Verbose -Message "Parsing $Path" $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr) $asttokens.where( {$_.kind -eq 'comment'}) | Select-Object -ExpandProperty Text $ast } End {Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"} } function Merge-Module { [CmdletBinding()] param ([string] $ModuleName, [string] $ModulePathSource, [string] $ModulePathTarget, [Parameter(Mandatory = $false, ValueFromPipeline = $false)] [ValidateSet("ASC", "DESC", "NONE")] [string] $Sort = 'NONE', [string[]] $FunctionsToExport, [string[]] $AliasesToExport, [Array] $LibrariesCore, [Array] $LibrariesDefault, $FormatCodePSM1, $FormatCodePSD1) $PSM1FilePath = "$ModulePathTarget\$ModuleName.psm1" $PSD1FilePath = "$ModulePathTarget\$ModuleName.psd1" if ($PSEdition -eq 'Core') {$ScriptFunctions = Get-ChildItem -Path $ModulePathSource\*.ps1 -ErrorAction SilentlyContinue -Recurse -FollowSymlink} else {$ScriptFunctions = Get-ChildItem -Path $ModulePathSource\*.ps1 -ErrorAction SilentlyContinue -Recurse} if ($Sort -eq 'ASC') {$ScriptFunctions = $ScriptFunctions | Sort-Object -Property Name} elseif ($Sort -eq 'DESC') {$ScriptFunctions = $ScriptFunctions | Sort-Object -Descending -Property Name} foreach ($FilePath in $ScriptFunctions) { $Content = Get-Content -Path $FilePath -Raw $Content = $Content.Replace('$PSScriptRoot\', '$PSScriptRoot\') $Content = $Content.Replace('$PSScriptRoot\', '$PSScriptRoot\') $Content | Out-File -Append -LiteralPath $PSM1FilePath -Encoding utf8 } $FilePathUsing = "$ModulePathTarget\$ModuleName.ps1" $UsingInPlace = Format-UsingNamespace -FilePath $PSM1FilePath -FilePathUsing $FilePathUsing if ($UsingInPlace) {Format-Code -FilePath $FilePathUsing -FormatCode $FormatCodePSM1} New-PSMFile -Path $PSM1FilePath -FunctionNames $FunctionsToExport -FunctionAliaes $AliasesToExport -LibrariesCore $LibrariesCore -LibrariesDefault $LibrariesDefault -ModuleName $ModuleName -UsingNamespaces:$UsingInPlace Format-Code -FilePath $PSM1FilePath -FormatCode $FormatCodePSM1 New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath Format-Code -FilePath $PSD1FilePath -FormatCode $FormatCodePSD1 } function New-CreateModule { [CmdletBinding()] param ([string] $ProjectName, $ModulePath, $ProjectPath) $FullProjectPath = "$projectPath\$projectName" $Folders = 'Private', 'Public', 'Examples', 'Ignore', 'Publish', 'Enums', 'Data' Add-Directory $FullProjectPath foreach ($folder in $Folders) {Add-Directory "$FullProjectPath\$folder"} Copy-File -Source "$PSScriptRoot\Data\Example-Gitignore.txt" -Destination "$FullProjectPath\.gitignore" Copy-File -Source "$PSScriptRoot\Data\Example-LicenseMIT.txt" -Destination "$FullProjectPath\License" Copy-File -Source "$PSScriptRoot\Data\Example-ModuleStarter.ps1" -Destination "$FullProjectPath\$ProjectName.psm1" } function New-PersonalManifest { [CmdletBinding()] param([System.Collections.IDictionary] $Configuration, [string] $ManifestPath, [switch] $AddScriptsToProcess) $Manifest = $Configuration.Information.Manifest $Manifest.Path = $ManifestPath if (-not $AddScriptsToProcess) {$Manifest.ScriptsToProcess = @()} New-ModuleManifest @Manifest if ($Configuration.Steps.PublishModule.Prerelease -ne '') { $Data = Import-PowerShellDataFile -Path $Configuration.Information.Manifest.Path if ($Data.ScriptsToProcess.Count -eq 0) {$Data.Remove('ScriptsToProcess')} if ($Data.CmdletsToExport.Count -eq 0) {$Data.Remove('CmdletsToExport')} $Data.PrivateData.PSData.Prerelease = $Configuration.Steps.PublishModule.Prerelease $Data | Export-PSData -DataFile $Configuration.Information.Manifest.Path } Write-Verbose "Converting $($Configuration.Information.Manifest.Path)" (Get-Content $Manifest.Path) | Out-FileUtf8NoBom $Manifest.Path } function New-PrepareManifest { [CmdletBinding()] param($ProjectName, $modulePath, $projectPath, $functionToExport, $projectUrl) Set-Location "$projectPath\$ProjectName" $manifest = @{Path = ".\$ProjectName.psd1" RootModule = "$ProjectName.psm1" Author = 'Przemyslaw Klys' CompanyName = 'Evotec' Copyright = 'Evotec (c) 2011-2019. All rights reserved.' Description = "Simple project" FunctionsToExport = $functionToExport CmdletsToExport = '' VariablesToExport = '' AliasesToExport = '' FileList = "$ProjectName.psm1", "$ProjectName.psd1" HelpInfoURI = $projectUrl ProjectUri = $projectUrl } New-ModuleManifest @manifest } function New-PrepareModule { [CmdletBinding()] param ([System.Collections.IDictionary] $Configuration) if (-not $Configuration) {return} if (-not $Configuration.Information.DirectoryModulesCore) {$Configuration.Information.DirectoryModulesCore = "$Env:USERPROFILE\Documents\PowerShell\Modules"} if (-not $Configuration.Information.DirectoryModules) {$Configuration.Information.DirectoryModules = "$Env:USERPROFILE\Documents\WindowsPowerShell\Modules"} if ($Configuration.Steps.BuildModule.Enable) {Start-ModuleBuilding -Configuration $Configuration -Core:$false} if ($Configuration.Steps.BuildModule.EnableDesktop) {Start-ModuleBuilding -Configuration $Configuration -Core:$false} if ($Configuration.Steps.BuildModule.EnableCore) {Start-ModuleBuilding -Configuration $Configuration -Core:$true} } function New-PSMFile { [cmdletbinding()] param([string] $Path, [string[]] $FunctionNames, [string[]] $FunctionAliaes, [Array] $LibrariesCore, [Array] $LibrariesDefault, [string] $ModuleName, [switch] $UsingNamespaces) if ($FunctionNames.Count -gt 0) { $Functions = ($FunctionNames | Sort-Object -Unique) -join "','" $Functions = "'$Functions'" } else {$Functions = @()} if ($FunctionAliaes.Count -gt 0) { $Aliases = ($FunctionAliaes | Sort-Object -Unique) -join "','" $Aliases = "'$Aliases'" } else {$Aliases = @()} "" | Add-Content -Path $Path if ($LibrariesCore.Count -gt 0 -and $LibrariesDefault.Count -gt 0) { 'if ($PSEdition -eq ''Core'') {' | Add-Content -Path $Path foreach ($File in $LibrariesCore) { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } '} else {' | Add-Content -Path $Path foreach ($File in $LibrariesDefault) { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } '}' | Add-Content -Path $Path } elseif ($LibrariesCore.Count -gt 0) { foreach ($File in $LibrariesCore) { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } } elseif ($LibrariesDefault.Count -gt 0) { foreach ($File in $LibrariesDefault) { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } } if ($UsingNamespaces) {'. $PSScriptRoot\' + "$ModuleName.ps1" | Add-Content -Path $Path} @" Export-ModuleMember `` -Function @($Functions) `` -Alias @($Aliases) "@ | Add-Content -Path $Path } function New-PublishModule { [cmdletbinding()] param($projectName, $apikey, [bool] $RequireForce) Publish-Module -Name $projectName -Repository PSGallery -NuGetApiKey $apikey -Force:$RequireForce -verbose } <# .SYNOPSIS Outputs to a UTF-8-encoded file *without a BOM* (byte-order mark). .DESCRIPTION Mimics the most important aspects of Out-File: * Input objects are sent to Out-String first. * -Append allows you to append to an existing file, -NoClobber prevents overwriting of an existing file. * -Width allows you to specify the line width for the text representations of input objects that aren't strings. However, it is not a complete implementation of all Out-String parameters: * Only a literal output path is supported, and only as a parameter. * -Force is not supported. Caveat: *All* pipeline input is buffered before writing output starts, but the string representations are generated and written to the target file one by one. .NOTES The raison d'être for this advanced function is that, as of PowerShell v5, Out-File still lacks the ability to write UTF-8 files without a BOM: using -Encoding UTF8 invariably prepends a BOM. #> function Out-FileUtf8NoBom { [CmdletBinding()] param([Parameter(Mandatory, Position = 0)] [string] $LiteralPath, [switch] $Append, [switch] $NoClobber, [AllowNull()] [int] $Width, [Parameter(ValueFromPipeline)] $InputObject) [System.IO.Directory]::SetCurrentDirectory($PWD) $LiteralPath = [IO.Path]::GetFullPath($LiteralPath) if ($NoClobber -and (Test-Path $LiteralPath)) {Throw [IO.IOException] "The file '$LiteralPath' already exists."} $sw = New-Object IO.StreamWriter $LiteralPath, $Append $htOutStringArgs = @{} if ($Width) {$htOutStringArgs += @{Width = $Width} } try {$Input | Out-String -Stream @htOutStringArgs | ForEach-Object {$sw.WriteLine($_)}} finally {$sw.Dispose()} } function Remove-Comments { Param ([string] $FilePath, [parameter(ValueFromPipeline = $True)] $Scriptblock, [string] $ScriptContent) Begin { $Items = @() if ($PSBoundParameters['FilePath']) { $ScriptBlockString = [IO.File]::ReadAllText((Resolve-Path $FilePath)) $ScriptBlock = [ScriptBlock]::Create($ScriptBlockString) } elseif ($PSBoundParameters['ScriptContent']) {$ScriptBlock = [ScriptBlock]::Create($ScriptContent)} else {} } Process {$Items += $Scriptblock} End { $OldScript = $Items -join [environment]::NewLine If (-not $OldScript.Trim(" `n`r`t")) {return} $Tokens = [System.Management.Automation.PSParser]::Tokenize($OldScript, [ref]$Null) $AllowedComments = @('requires' '.SYNOPSIS' '.DESCRIPTION' '.PARAMETER' '.EXAMPLE' '.INPUTS' '.OUTPUTS' '.NOTES' '.LINK' '.COMPONENT' '.ROLE' '.FUNCTIONALITY' '.FORWARDHELPCATEGORY' '.REMOTEHELPRUNSPACE' '.EXTERNALHELP') $Tokens = $Tokens.ForEach{If ($_.Type -ne 'Comment') {$_} Else { $CommentText = $_.Content.Substring($_.Content.IndexOf('#') + 1) $FirstInnerToken = [System.Management.Automation.PSParser]::Tokenize($CommentText, [ref]$Null) | Where-Object {$_.Type -ne 'NewLine'} | Select-Object -First 1 If ($FirstInnerToken.Content -in $AllowedComments) {$_} }} $NewScriptText = '' $SkipNext = $False If ($Tokens.Count -gt 1) { ForEach ($i in (0..($Tokens.Count - 2))) { If (-not $SkipNext -and $Tokens[$i ].Type -ne 'LineContinuation' -and ($Tokens[$i ].Type -notin ('NewLine', 'StatementSeparator') -or $Tokens[$i + 1].Type -notin ('NewLine', 'StatementSeparator', 'GroupEnd'))) { If ($Tokens[$i].Type -in ('String', 'Variable')) {$NewScriptText += $OldScript.Substring($Tokens[$i].Start, $Tokens[$i].Length)} Else {$NewScriptText += $Tokens[$i].Content} If ($Tokens[$i ].Type -notin ('NewLine', 'GroupStart', 'StatementSeparator') -and $Tokens[$i + 1].Type -notin ('NewLine', 'GroupEnd', 'StatementSeparator') -and $Tokens[$i].EndLine -eq $Tokens[$i + 1].StartLine -and $Tokens[$i + 1].StartColumn - $Tokens[$i].EndColumn -gt 0) {$NewScriptText += ' '} $SkipNext = $Tokens[$i].Type -eq 'GroupStart' -and $Tokens[$i + 1].Type -in ('NewLine', 'StatementSeparator') } Else {$SkipNext = $SkipNext -and $Tokens[$i + 1].Type -in ('NewLine', 'StatementSeparator')} } } If ($Tokens) {If ($Tokens[$i].Type -in ('String', 'Variable')) {$NewScriptText += $OldScript.Substring($Tokens[-1].Start, $Tokens[-1].Length)} Else {$NewScriptText += $Tokens[-1].Content}} $NewScriptText = $NewScriptText.TrimStart("`n`r;") If ($Items.Count -eq 1) {If ($Items[0] -is [scriptblock]) {return [scriptblock]::Create($NewScriptText)} Else {return $NewScriptText}} Else {return $NewScriptText.Split("`n`r", [System.StringSplitOptions]::RemoveEmptyEntries)} } } function Remove-Directory { [CmdletBinding()] param ([string] $Dir) if (-not [string]::IsNullOrWhiteSpace($Dir)) { $exists = Test-Path -Path $Dir if ($exists) {Remove-Item $dir -Confirm:$false -Recurse} else {} } } $Script:FormatterSettings = @{IncludeRules = @('PSPlaceOpenBrace', 'PSPlaceCloseBrace', 'PSUseConsistentWhitespace', 'PSUseConsistentIndentation', 'PSAlignAssignmentStatement', 'PSUseCorrectCasing') Rules = @{PSPlaceOpenBrace = @{Enable = $true OnSameLine = $true NewLineAfter = $true IgnoreOneLineBlock = $true } PSPlaceCloseBrace = @{Enable = $true NewLineAfter = $false IgnoreOneLineBlock = $true NoEmptyLineBefore = $false } PSUseConsistentIndentation = @{Enable = $true Kind = 'space' PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline' IndentationSize = 4 } PSUseConsistentWhitespace = @{Enable = $true CheckInnerBrace = $true CheckOpenBrace = $true CheckOpenParen = $true CheckOperator = $true CheckPipe = $true CheckSeparator = $true } PSUseCorrectCasing = @{Enable = $true} } } function Set-LinkedFiles { [CmdletBinding()] param($LinkFiles, $FullModulePath, $FullProjectPath, [switch] $Delete) foreach ($file in $LinkFiles) { $Path = "$FullModulePath\$file" $Path2 = "$FullProjectPath\$file" if ($Delete) { if (Test-ReparsePoint -path $Path) { Write-Verbose "Removing symlink first $path" Remove-Item $Path -Confirm:$false } } Write-Verbose "Creating symlink from $path2 (source) to $path (target)" $linkingFiles = cmd /c mklink $path $path2 } } function Start-ModuleBuilding { [CmdletBinding()] param([System.Collections.IDictionary] $Configuration, [switch] $Core) if ($Core) {[string] $FullModulePath = [IO.path]::Combine($Configuration.Information.DirectoryModulesCore, $Configuration.Information.ModuleName)} else {[string] $FullModulePath = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName)} [string] $FullTemporaryPath = [IO.path]::GetTempPath() + '' + $Configuration.Information.ModuleName [string] $FullProjectPath = [IO.Path]::Combine($Configuration.Information.DirectoryProjects, $Configuration.Information.ModuleName) [string] $ProjectName = $Configuration.Information.ModuleName Write-Verbose '----------------------------------------------------' Write-Verbose "Project Name: $ProjectName" Write-Verbose "Full module path: $FullModulePath" Write-Verbose "Full project path: $FullProjectPath" Write-Verbose "Full module path to delete: $FullModulePathDelete" Write-Verbose "Full temporary path: $FullTemporaryPath" Write-Verbose "PSScriptRoot: $PSScriptRoot" Write-Verbose "PSEdition: $PSEdition" Write-Verbose '----------------------------------------------------' $CurrentLocation = (Get-Location).Path Set-Location -Path $FullProjectPath Remove-Directory $FullModulePath Remove-Directory $FullTemporaryPath Add-Directory $FullModulePath Add-Directory $FullTemporaryPath $DirectoryTypes = 'Public', 'Private', 'Lib', 'Bin', 'Enums', 'Images', 'Templates', 'Resources' $LinkDirectories = @() $LinkPrivatePublicFiles = @() $Configuration.Information.Manifest.RootModule = "$($ProjectName).psm1" $Configuration.Information.Manifest.FunctionsToExport = @() $Configuration.Information.Manifest.CmdletsToExport = @() $Configuration.Information.Manifest.VariablesToExport = @() $Configuration.Information.Manifest.AliasesToExport = @() if ($Configuration.Steps.BuildModule) { if ($PSEdition -eq 'core') { $Directories = Get-ChildItem -Path $FullProjectPath -Directory -Recurse -FollowSymlink $Files = Get-ChildItem -Path $FullProjectPath -File -Recurse -FollowSymlink $FilesRoot = Get-ChildItem -Path $FullProjectPath -File -FollowSymlink } else { $Directories = Get-ChildItem -Path $FullProjectPath -Directory -Recurse $Files = Get-ChildItem -Path $FullProjectPath -File -Recurse $FilesRoot = Get-ChildItem -Path $FullProjectPath -File } $LinkDirectories = foreach ($directory in $Directories) { $RelativeDirectoryPath = (Resolve-Path -LiteralPath $directory.FullName -Relative).Replace('.\', '') $RelativeDirectoryPath = "$RelativeDirectoryPath\" foreach ($LookupDir in $DirectoryTypes) {if ($RelativeDirectoryPath -like "$LookupDir\*") {Add-ObjectTo -Object $RelativeDirectoryPath -Type 'Directory List'}} } $AllFiles = foreach ($File in $Files) { $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('.\', '') $RelativeFilePath } $RootFiles = foreach ($File in $FilesRoot) { $RelativeFilePath = (Resolve-Path -LiteralPath $File.FullName -Relative).Replace('.\', '') $RelativeFilePath } $LinkFilesRoot = foreach ($File in $RootFiles) { switch -Wildcard ($file) { '*.psd1' {Add-ObjectTo -Object $File -Type 'Root Files List'} '*.psm1' {Add-ObjectTo -Object $File -Type 'Root Files List'} 'License*' {Add-ObjectTo -Object $File -Type 'Root Files List'} } } $LinkPrivatePublicFiles = foreach ($file in $AllFiles) { switch -Wildcard ($file) { "*.dll" {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Lib'} "*.exe" {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Bin'} '*.ps1' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Private', 'Public', 'Enums'} '*license*' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Lib', 'Resources'} '*.jpg' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Images', 'Resources'} '*.png' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Images', 'Resources'} '*.xml' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Templates'} '*.docx' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Templates'} '*.js' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'} '*.css' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'} '*.rcs' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'} '*.gif' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'} '*.html' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'} '*.txt' {Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Resources'} } } if ($Configuration.Information.Manifest) { $Functions = Get-FunctionNamesFromFolder -FullProjectPath $FullProjectPath -Folder $Configuration.Information.FunctionsToExport if ($Functions) { Write-Verbose "Functions export: $Functions" $Configuration.Information.Manifest.FunctionsToExport = $Functions } $Aliases = Get-FunctionAliasesFromFolder -FullProjectPath $FullProjectPath -Folder $Configuration.Information.AliasesToExport if ($Aliases) { Write-Verbose "Aliases export: $Aliases" $Configuration.Information.Manifest.AliasesToExport = $Aliases } if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.ScriptsToProcess)) { $StartsWithEnums = "$($Configuration.Information.ScriptsToProcess)\" $FilesEnums = $LinkPrivatePublicFiles | Where-Object {($_).StartsWith($StartsWithEnums)} if ($FilesEnums.Count -gt 0) { Write-Verbose "ScriptsToProcess export: $FilesEnums" $Configuration.Information.Manifest.ScriptsToProcess = $FilesEnums } } $PSD1FilePath = "$FullProjectPath\$ProjectName.psd1" New-PersonalManifest -Configuration $Configuration -ManifestPath $PSD1FilePath -AddScriptsToProcess Format-Code -FilePath $PSD1FilePath -FormatCode $Configuration.Options.Standard.FormatCodePSD1 } if ($Configuration.Steps.BuildModule.Merge) { foreach ($Directory in $LinkDirectories) { $Dir = "$FullTemporaryPath\$Directory" Add-Directory $Dir } $LinkDirectoriesWithSupportFiles = $LinkDirectories | Where-Object {$_ -ne 'Public\' -and $_ -ne 'Private\'} foreach ($Directory in $LinkDirectoriesWithSupportFiles) { $Dir = "$FullModulePath\$Directory" Add-Directory $Dir } Write-Verbose '[+] Linking files from Root Dir' Set-LinkedFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath Write-Verbose '[+] Linking files from Sub Dir' Set-LinkedFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath $FilesToLink = $LinkPrivatePublicFiles | Where-Object {$_ -notlike '*.ps1' -and $_ -notlike '*.psd1'} Set-LinkedFiles -LinkFiles $FilesToLink -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesCore)) { $StartsWithCore = "$($Configuration.Information.LibrariesCore)\" $FilesLibrariesCore = $LinkPrivatePublicFiles | Where-Object {($_).StartsWith($StartsWithCore)} } if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.LibrariesDefault)) { $StartsWithDefault = "$($Configuration.Information.LibrariesDefault)\" $FilesLibrariesDefault = $LinkPrivatePublicFiles | Where-Object {($_).StartsWith($StartsWithDefault)} } Merge-Module -ModuleName $ProjectName -ModulePathSource $FullTemporaryPath -ModulePathTarget $FullModulePath -Sort $Configuration.Options.Merge.Sort -FunctionsToExport $Configuration.Information.Manifest.FunctionsToExport -AliasesToExport $Configuration.Information.Manifest.AliasesToExport -LibrariesCore $FilesLibrariesCore -LibrariesDefault $FilesLibrariesDefault -FormatCodePSM1 $Configuration.Options.Merge.FormatCodePSM1 -FormatCodePSD1 $Configuration.Options.Merge.FormatCodePSD1 } else { foreach ($Directory in $LinkDirectories) { $Dir = "$FullModulePath\$Directory" Add-Directory $Dir } Write-Verbose '[+] Linking files from Root Dir' Set-LinkedFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath Write-Verbose '[+] Linking files from Sub Dir' Set-LinkedFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath } } if ($Configuration.Steps.PublishModule.Enabled) { if ($Configuration.Options.PowerShellGallery.FromFile) { $ApiKey = Get-Content -Path $Configuration.Options.PowerShellGallery.ApiKey New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $ApiKey -RequireForce $Configuration.Steps.PublishModule.RequireForce } else {New-PublishModule -ProjectName $Configuration.Information.ModuleName -ApiKey $Configuration.Options.PowerShellGallery.ApiKey -RequireForce $Configuration.Steps.PublishModule.RequireForce} } Set-Location -Path $CurrentLocation if ($Configuration) { if ($Configuration.Options.ImportModules.RequiredModules) {foreach ($Module in $Configuration.Information.Manifest.RequiredModules) {Import-Module -Name $Module -Force -ErrorAction Stop -Verbose:$Configuration.Options.ImportModules.Verbose}} if ($Configuration.Options.ImportModules.Self) {Import-Module -Name $ProjectName -Force -ErrorAction Stop} if ($Configuration.Steps.BuildDocumentation) { $DocumentationPath = "$FullProjectPath\$($Configuration.Options.Documentation.Path)" $ReadMePath = "$FullProjectPath\$($Configuration.Options.Documentation.PathReadme)" Write-Verbose "Generating documentation to $DocumentationPath with $ReadMePath" if (-not (Test-Path -Path $DocumentationPath)) {$null = New-Item -Path "$FullProjectPath\Docs" -ItemType Directory -Force} $Files = Get-ChildItem -Path $DocumentationPath if ($Files.Count -gt 0) {$null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath -ErrorAction Stop} else { $null = New-MarkdownHelp -Module $ProjectName -WithModulePage -OutputFolder $DocumentationPath -ErrorAction Stop $null = Move-Item -Path "$DocumentationPath\$ProjectName.md" -Destination $ReadMePath if ($Configuration.Options.Documentation.UpdateWhenNew) {$null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath -ErrorAction Stop} } } } } function Test-ReparsePoint { [CmdletBinding()] param ([string]$path) $file = Get-Item $path -Force -ea SilentlyContinue return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint) } function Write-PowerShellHashtable { [cmdletbinding()] <# .Synopsis Takes an creates a script to recreate a hashtable .Description Allows you to take a hashtable and create a hashtable you would embed into a script. Handles nested hashtables and indents nested hashtables automatically. .Parameter inputObject The hashtable to turn into a script .Parameter scriptBlock Determines if a string or a scriptblock is returned .Example # Corrects the presentation of a PowerShell hashtable @{Foo='Bar';Baz='Bing';Boo=@{Bam='Blang'}} | Write-PowerShellHashtable .Outputs [string] .Outputs [ScriptBlock] .Link https://github.com/StartAutomating/Pipeworks about_hash_tables #> [OutputType([string], [ScriptBlock])] param([Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [PSObject] $InputObject, [Alias('ScriptBlock')] [switch]$AsScriptBlock, [Switch]$Sort) process { $callstack = @(foreach ($_ in (Get-PSCallStack)) {if ($_.Command -eq "Write-PowerShellHashtable") {$_}}) $depth = $callStack.Count if ($inputObject -isnot [Hashtable]) {$newInputObject = @{PSTypeName = @($inputobject.pstypenames)[-1]} foreach ($prop in $inputObject.psobject.properties) {$newInputObject[$prop.Name] = $prop.Value} $inputObject = $newInputObject } if ($inputObject -is [Hashtable]) { $scriptString = "" $indent = $depth * 4 $scriptString += "@{ " $items = $inputObject.GetEnumerator() if ($Sort) {$items = $items | Sort-Object Key} foreach ($kv in $items) { $scriptString += " " * $indent $keyString = "$($kv.Key)" if ($keyString.IndexOfAny(" _.#-+:;()'!?^@#$%&".ToCharArray()) -ne -1) {if ($keyString.IndexOf("'") -ne -1) {$scriptString += "'$($keyString.Replace("'","''"))'="} else {$scriptString += "'$keyString'="}} elseif ($keyString) {$scriptString += "$keyString="} $value = $kv.Value if ($value -is [string]) {$value = "'" + $value.Replace("'", "''").Replace("’", "’’").Replace("‘", "‘‘") + "'"} elseif ($value -is [ScriptBlock]) {$value = "{$value}"} elseif ($value -is [switch]) {$value = if ($value) {'$true'} else {'$false'}} elseif ($value -is [DateTime]) {$value = if ($value) {"[DateTime]'$($value.ToString("o"))'"}} elseif ($value -is [bool]) {$value = if ($value) {'$true'} else {'$false'}} elseif ($value -and $value.GetType -and ($value.GetType().IsArray -or $value -is [Collections.IList])) { $value = foreach ($v in $value) {if ($v -is [Hashtable]) {Write-PowerShellHashtable $v} elseif ($v -is [Object] -and $v -isnot [string]) {Write-PowerShellHashtable $v} else {("'" + "$v".Replace("'", "''").Replace("’", "’’").Replace("‘", "‘‘") + "'")}} $oldOfs = $ofs $ofs = ",$(' ' * ($indent + 4))" $value = "$value" $ofs = $oldOfs } elseif ($value -as [Hashtable[]]) { $value = foreach ($v in $value) {Write-PowerShellHashtable $v} $value = $value -join "," } elseif ($value -is [Hashtable]) {$value = "$(Write-PowerShellHashtable $value)"} elseif ($value -as [Double]) {$value = "$value"} else { $valueString = "'$value'" if ($valueString[0] -eq "'" -and $valueString[1] -eq "@" -and $valueString[2] -eq "{") {$value = Write-PowerShellHashtable -InputObject $value} else {$value = $valueString} } $scriptString += "$value " } $scriptString += " " * ($depth - 1) * 4 $scriptString += "}" if ($AsScriptBlock) {[ScriptBlock]::Create($scriptString)} else {$scriptString} } } } Export-ModuleMember -Function @('New-PrepareModule') -Alias @() |