PSPublishModule.psm1
function Compress-Archive { [CmdletBinding(DefaultParameterSetName = "Path", SupportsShouldProcess = $true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=393252")] param ([parameter (mandatory = $true, Position = 0, ParameterSetName = "Path", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [parameter (mandatory = $true, Position = 0, ParameterSetName = "PathWithForce", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [parameter (mandatory = $true, Position = 0, ParameterSetName = "PathWithUpdate", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string[]] $Path, [parameter (mandatory = $true, ParameterSetName = "LiteralPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)] [parameter (mandatory = $true, ParameterSetName = "LiteralPathWithForce", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)] [parameter (mandatory = $true, ParameterSetName = "LiteralPathWithUpdate", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias("PSPath")] [string[]] $LiteralPath, [parameter (mandatory = $true, Position = 1, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [ValidateNotNullOrEmpty()] [string] $DestinationPath, [parameter (mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [ValidateSet("Optimal", "NoCompression", "Fastest")] [string] $CompressionLevel = "Optimal", [parameter(mandatory = $true, ParameterSetName = "PathWithUpdate", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [parameter(mandatory = $true, ParameterSetName = "LiteralPathWithUpdate", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [switch] $Update = $false, [parameter(mandatory = $true, ParameterSetName = "PathWithForce", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [parameter(mandatory = $true, ParameterSetName = "LiteralPathWithForce", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [switch] $Force = $false) BEGIN { $inputPaths = @() $destinationParentDir = [system.IO.Path]::GetDirectoryName($DestinationPath) if ($null -eq $destinationParentDir) { $errorMessage = ($LocalizedData.InvalidDestinationPath -f $DestinationPath) ThrowTerminatingErrorHelper "InvalidArchiveFilePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath } if ($destinationParentDir -eq [string]::Empty) { $destinationParentDir = '.' } $achiveFileName = [system.IO.Path]::GetFileName($DestinationPath) $destinationParentDir = GetResolvedPathHelper $destinationParentDir $false $PSCmdlet if ($destinationParentDir.Count -gt 1) { $errorMessage = ($LocalizedData.InvalidArchiveFilePathError -f $DestinationPath, "DestinationPath", "DestinationPath") ThrowTerminatingErrorHelper "InvalidArchiveFilePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath } IsValidFileSystemPath $destinationParentDir | Out-Null $DestinationPath = Join-Path -Path $destinationParentDir -ChildPath $achiveFileName $extension = [system.IO.Path]::GetExtension($DestinationPath) If ($extension -eq [string]::Empty) { $DestinationPathWithOutExtension = $DestinationPath $DestinationPath = $DestinationPathWithOutExtension + $zipFileExtension $appendArchiveFileExtensionMessage = ($LocalizedData.AppendArchiveFileExtensionMessage -f $DestinationPathWithOutExtension, $DestinationPath) Write-Verbose $appendArchiveFileExtensionMessage } else { if ($extension -ne $zipFileExtension) { $errorMessage = ($LocalizedData.InvalidZipFileExtensionError -f $extension, $zipFileExtension) ThrowTerminatingErrorHelper "NotSupportedArchiveFileExtension" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $extension } } $archiveFileExist = Test-Path -LiteralPath $DestinationPath -PathType Leaf if ($archiveFileExist -and ($Update -eq $false -and $Force -eq $false)) { $errorMessage = ($LocalizedData.ZipFileExistError -f $DestinationPath) ThrowTerminatingErrorHelper "ArchiveFileExists" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $DestinationPath } if ($archiveFileExist -and $Update -eq $true) { $item = Get-Item -Path $DestinationPath if ($item.Attributes.ToString().Contains("ReadOnly")) { $errorMessage = ($LocalizedData.ArchiveFileIsReadOnly -f $DestinationPath) ThrowTerminatingErrorHelper "ArchiveFileIsReadOnly" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidOperation) $DestinationPath } } $isWhatIf = $psboundparameters.ContainsKey("WhatIf") if (!$isWhatIf) { $preparingToCompressVerboseMessage = ($LocalizedData.PreparingToCompressVerboseMessage) Write-Verbose $preparingToCompressVerboseMessage $progressBarStatus = ($LocalizedData.CompressProgressBarText -f $DestinationPath) ProgressBarHelper "Compress-Archive" $progressBarStatus 0 100 100 1 } } PROCESS { if ($PsCmdlet.ParameterSetName -eq "Path" -or $PsCmdlet.ParameterSetName -eq "PathWithForce" -or $PsCmdlet.ParameterSetName -eq "PathWithUpdate") { $inputPaths += $Path } if ($PsCmdlet.ParameterSetName -eq "LiteralPath" -or $PsCmdlet.ParameterSetName -eq "LiteralPathWithForce" -or $PsCmdlet.ParameterSetName -eq "LiteralPathWithUpdate") { $inputPaths += $LiteralPath } } END { if (($PsCmdlet.ParameterSetName -eq "PathWithForce" -or $PsCmdlet.ParameterSetName -eq "LiteralPathWithForce") -and $archiveFileExist) { Remove-Item -Path $DestinationPath -Force -ErrorAction Stop } $isLiteralPathUsed = $false if ($PsCmdlet.ParameterSetName -eq "LiteralPath" -or $PsCmdlet.ParameterSetName -eq "LiteralPathWithForce" -or $PsCmdlet.ParameterSetName -eq "LiteralPathWithUpdate") { $isLiteralPathUsed = $true } ValidateDuplicateFileSystemPath $PsCmdlet.ParameterSetName $inputPaths $resolvedPaths = GetResolvedPathHelper $inputPaths $isLiteralPathUsed $PSCmdlet IsValidFileSystemPath $resolvedPaths | Out-Null $sourcePath = $resolvedPaths $sourcePathInCsvFormat = CSVHelper $sourcePath if ($pscmdlet.ShouldProcess($sourcePathInCsvFormat)) { try { $isArchiveFileProcessingComplete = $false $numberOfItemsArchived = CompressArchiveHelper $sourcePath $DestinationPath $CompressionLevel $Update $isArchiveFileProcessingComplete = $true } finally { if (($isArchiveFileProcessingComplete -eq $false) -or ($numberOfItemsArchived -eq 0)) { $DeleteArchiveFileMessage = ($LocalizedData.DeleteArchiveFile -f $DestinationPath) Write-Verbose $DeleteArchiveFileMessage if (Test-Path $DestinationPath) { Remove-Item -LiteralPath $DestinationPath -Force -Recurse -ErrorAction SilentlyContinue } } } } } } function Import-PowerShellDataFile { [CmdletBinding(DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=623621")] [OutputType("System.Collections.Hashtable")] param([Parameter(ParameterSetName = "ByPath", Position = 0)] [String[]] $Path, [Parameter(ParameterSetName = "ByLiteralPath", ValueFromPipelineByPropertyName = $true)] [Alias("PSPath")] [String[]] $LiteralPath) begin { function ThrowInvalidDataFile { param($resolvedPath, $extraError) $errorId = "CouldNotParseAsPowerShellDataFile$extraError" $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::CouldNotParseAsPowerShellDataFile -f $resolvedPath $exception = [System.InvalidOperationException]::New($errorMessage) $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) $PSCmdlet.WriteError($errorRecord) } } process { foreach ($resolvedPath in (Resolve-Path @PSBoundParameters)) { $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile(($resolvedPath.ProviderPath), [ref] $null, [ref] $parseErrors) if ($parseErrors.Length -gt 0) { ThrowInvalidDataFile $resolvedPath } else { $data = $ast.Find( { $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $false) if ($data) { $data.SafeGetValue() } else { ThrowInvalidDataFile $resolvedPath "NoHashtableRoot" } } } } } function New-MarkdownHelp { [CmdletBinding()] [OutputType([System.IO.FileInfo[]])] param([Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "FromModule")] [string[]]$Module, [Parameter(Mandatory = $true, ParameterSetName = "FromCommand")] [string[]]$Command, [Parameter(Mandatory = $true, ParameterSetName = "FromMaml")] [string[]]$MamlFile, [Parameter(ParameterSetName = "FromModule")] [Parameter(ParameterSetName = "FromCommand")] [System.Management.Automation.Runspaces.PSSession]$Session, [Parameter(ParameterSetName = "FromMaml")] [switch]$ConvertNotesToList, [Parameter(ParameterSetName = "FromMaml")] [switch]$ConvertDoubleDashLists, [switch]$Force, [switch]$AlphabeticParamsOrder, [hashtable]$Metadata, [Parameter(ParameterSetName = "FromCommand")] [string]$OnlineVersionUrl = '', [Parameter(Mandatory = $true)] [string]$OutputFolder, [switch]$NoMetadata, [switch]$UseFullTypeName, [System.Text.Encoding]$Encoding = $script:UTF8_NO_BOM, [Parameter(ParameterSetName = "FromModule")] [Parameter(ParameterSetName = "FromMaml")] [switch]$WithModulePage, [Parameter(ParameterSetName = "FromModule")] [Parameter(ParameterSetName = "FromMaml")] [string]$ModulePagePath, [Parameter(ParameterSetName = "FromModule")] [Parameter(ParameterSetName = "FromMaml")] [string] $Locale = "en-US", [Parameter(ParameterSetName = "FromModule")] [Parameter(ParameterSetName = "FromMaml")] [string] $HelpVersion = $LocalizedData.HelpVersion, [Parameter(ParameterSetName = "FromModule")] [Parameter(ParameterSetName = "FromMaml")] [string] $FwLink = $LocalizedData.FwLink, [Parameter(ParameterSetName = "FromMaml")] [string] $ModuleName = "MamlModule", [Parameter(ParameterSetName = "FromMaml")] [string] $ModuleGuid = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", [switch] $ExcludeDontShow) begin { validateWorkingProvider New-Item -Type Directory $OutputFolder -ErrorAction SilentlyContinue > $null } process { function updateMamlObject { param([Parameter(Mandatory = $true)] [Markdown.MAML.Model.MAML.MamlCommand]$MamlCommandObject) if ($MamlCommandObject.Examples.Count -eq 0) { $MamlExampleObject = New-Object -TypeName Markdown.MAML.Model.MAML.MamlExample $MamlExampleObject.Title = $LocalizedData.ExampleTitle $MamlExampleObject.Code = @(New-Object -TypeName Markdown.MAML.Model.MAML.MamlCodeBlock ($LocalizedData.ExampleCode, 'powershell')) $MamlExampleObject.Remarks = $LocalizedData.ExampleRemark $MamlCommandObject.Examples.Add($MamlExampleObject) } if ($AlphabeticParamsOrder) { SortParamsAlphabetically $MamlCommandObject } } function processMamlObjectToFile { param([Parameter(ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [Markdown.MAML.Model.MAML.MamlCommand]$mamlObject) process { updateMamlObject $mamlObject if (-not $OnlineVersionUrl) { $onlineLink = $mamlObject.Links | Select-Object -First 1 if ($onlineLink) { $online = $onlineLink.LinkUri if ($onlineLink.LinkName -eq $script:MAML_ONLINE_LINK_DEFAULT_MONIKER -or $onlineLink.LinkName -eq $onlineLink.LinkUri) { $mamlObject.Links.Remove($onlineLink) > $null } } } else { $online = $OnlineVersionUrl } $commandName = $mamlObject.Name if ($NoMetadata) { $newMetadata = $null } else { if ($MamlFile) { $helpFileName = Split-Path -Leaf $MamlFile } else { if ($mamlObject.Name.EndsWith(".ps1")) { $getCommandName = Resolve-Path $Command } else { $getCommandName = $commandName } $a = @{Name = $getCommandName } if ($module) { $a['Module'] = $module } $helpFileName = GetHelpFileName (Get-Command @a) } Write-Verbose "Maml things module is: $($mamlObject.ModuleName)" $newMetadata = ($Metadata + @{$script:EXTERNAL_HELP_FILE_YAML_HEADER = $helpFileName $script:ONLINE_VERSION_YAML_HEADER = $online $script:MODULE_PAGE_MODULE_NAME = $mamlObject.ModuleName }) } $md = ConvertMamlModelToMarkdown -mamlCommand $mamlObject -metadata $newMetadata -NoMetadata:$NoMetadata MySetContent -path (Join-Path $OutputFolder "$commandName.md") -value $md -Encoding $Encoding -Force:$Force } } if ($NoMetadata -and $Metadata) { throw $LocalizedData.NoMetadataAndMetadata } if ($PSCmdlet.ParameterSetName -eq 'FromCommand') { $command | ForEach-Object { if (-not (Get-Command $_ -ErrorAction SilentlyContinue)) { throw $LocalizedData.CommandNotFound -f $_ } GetMamlObject -Session $Session -Cmdlet $_ -UseFullTypeName:$UseFullTypeName -ExcludeDontShow:$ExcludeDontShow.IsPresent | processMamlObjectToFile } } else { if ($module) { $iterator = $module } else { $iterator = $MamlFile } $iterator | ForEach-Object { if ($PSCmdlet.ParameterSetName -eq 'FromModule') { if (-not (GetCommands -AsNames -module $_)) { throw $LocalizedData.ModuleNotFound -f $_ } GetMamlObject -Session $Session -Module $_ -UseFullTypeName:$UseFullTypeName -ExcludeDontShow:$ExcludeDontShow.IsPresent | processMamlObjectToFile $ModuleName = $_ $ModuleGuid = (Get-Module $ModuleName).Guid $CmdletNames = GetCommands -AsNames -Module $ModuleName } else { if (-not (Test-Path $_)) { throw $LocalizedData.FileNotFound -f $_ } GetMamlObject -MamlFile $_ -ConvertNotesToList:$ConvertNotesToList -ConvertDoubleDashLists:$ConvertDoubleDashLists -ExcludeDontShow:$ExcludeDontShow.IsPresent | processMamlObjectToFile $CmdletNames += GetMamlObject -MamlFile $_ -ExcludeDontShow:$ExcludeDontShow.IsPresent | ForEach-Object { $_.Name } } if ($WithModulePage) { if (-not $ModuleGuid) { $ModuleGuid = "00000000-0000-0000-0000-000000000000" } if ($ModuleGuid.Count -gt 1) { Write-Warning -Message $LocalizedData.MoreThanOneGuid } NewModuleLandingPage -Path $OutputFolder -ModulePagePath $ModulePagePath -ModuleName $ModuleName -ModuleGuid $ModuleGuid -CmdletNames $CmdletNames -Locale $Locale -Version $HelpVersion -FwLink $FwLink -Encoding $Encoding -Force:$Force } } } } } function Publish-Module { <# .ExternalHelp PSModule-help.xml #> [CmdletBinding(SupportsShouldProcess = $true, PositionalBinding = $false, HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=398575', DefaultParameterSetName = "ModuleNameParameterSet")] Param ([Parameter(Mandatory = $true, ParameterSetName = "ModuleNameParameterSet", ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = "ModulePathParameterSet", ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string] $Path, [Parameter(ParameterSetName = "ModuleNameParameterSet")] [ValidateNotNullOrEmpty()] [string] $RequiredVersion, [Parameter()] [ValidateNotNullOrEmpty()] [string] $NuGetApiKey, [Parameter()] [ValidateNotNullOrEmpty()] [string] $Repository = $Script:PSGalleryModuleSource, [Parameter(ValueFromPipelineByPropertyName = $true)] [PSCredential] $Credential, [Parameter()] [ValidateSet("2.0")] [Version] $FormatVersion, [Parameter()] [string[]] $ReleaseNotes, [Parameter()] [ValidateNotNullOrEmpty()] [string[]] $Tags, [Parameter()] [ValidateNotNullOrEmpty()] [Uri] $LicenseUri, [Parameter()] [ValidateNotNullOrEmpty()] [Uri] $IconUri, [Parameter()] [ValidateNotNullOrEmpty()] [Uri] $ProjectUri, [Parameter(ParameterSetName = "ModuleNameParameterSet")] [ValidateNotNullOrEmpty()] [string[]] $Exclude, [Parameter()] [switch] $Force, [Parameter(ParameterSetName = "ModuleNameParameterSet")] [switch] $AllowPrerelease, [Parameter()] [switch] $SkipAutomaticTags) Begin { if ($LicenseUri -and -not (Test-WebUri -uri $LicenseUri)) { $message = $LocalizedData.InvalidWebUri -f ($LicenseUri, "LicenseUri") ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "InvalidWebUri" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $LicenseUri } if ($IconUri -and -not (Test-WebUri -uri $IconUri)) { $message = $LocalizedData.InvalidWebUri -f ($IconUri, "IconUri") ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "InvalidWebUri" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $IconUri } if ($ProjectUri -and -not (Test-WebUri -uri $ProjectUri)) { $message = $LocalizedData.InvalidWebUri -f ($ProjectUri, "ProjectUri") ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "InvalidWebUri" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $ProjectUri } Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe -Force:$Force } Process { if ($Repository -eq $Script:PSGalleryModuleSource) { $moduleSource = Get-PSRepository -Name $Repository -ErrorAction SilentlyContinue -WarningAction SilentlyContinue if (-not $moduleSource) { $message = $LocalizedData.PSGalleryNotFound -f ($Repository) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId 'PSGalleryNotFound' -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Repository return } } else { $ev = $null $moduleSource = Get-PSRepository -Name $Repository -ErrorVariable ev if ($ev) { return } } $DestinationLocation = $moduleSource.PublishLocation if (-not $DestinationLocation -or (-not (Microsoft.PowerShell.Management\Test-Path $DestinationLocation) -and -not (Test-WebUri -uri $DestinationLocation))) { $message = $LocalizedData.PSGalleryPublishLocationIsMissing -f ($Repository, $Repository) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "PSGalleryPublishLocationIsMissing" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Repository } $message = $LocalizedData.PublishLocation -f ($DestinationLocation) Write-Verbose -Message $message if (-not $NuGetApiKey.Trim()) { if (Microsoft.PowerShell.Management\Test-Path -Path $DestinationLocation) { $NuGetApiKey = "$(Get-Random)" } else { $message = $LocalizedData.NuGetApiKeyIsRequiredForNuGetBasedGalleryService -f ($Repository, $DestinationLocation) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "NuGetApiKeyIsRequiredForNuGetBasedGalleryService" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument } } $providerName = Get-ProviderName -PSCustomObject $moduleSource if ($providerName -ne $script:NuGetProviderName) { $message = $LocalizedData.PublishModuleSupportsOnlyNuGetBasedPublishLocations -f ($moduleSource.PublishLocation, $Repository, $Repository) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "PublishModuleSupportsOnlyNuGetBasedPublishLocations" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Repository } $moduleName = $null if ($Name) { if ($RequiredVersion) { $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet -Name $Name -RequiredVersion $RequiredVersion -AllowPrerelease:$AllowPrerelease if (-not $ValidationResult) { return } $reqResult = ValidateAndGet-VersionPrereleaseStrings -Version $RequiredVersion -CallerPSCmdlet $PSCmdlet if (-not $reqResult) { return } $reqVersion = $reqResult["Version"] $reqPrerelease = $reqResult["Prerelease"] } else { $reqVersion = $null $reqPrerelease = $null } $module = Microsoft.PowerShell.Core\Get-Module -ListAvailable -Name $Name -Verbose:$false | Microsoft.PowerShell.Core\Where-Object { $modInfoPrerelease = $null if ($_.PrivateData -and $_.PrivateData.GetType().ToString() -eq "System.Collections.Hashtable" -and $_.PrivateData["PSData"] -and $_.PrivateData.PSData.GetType().ToString() -eq "System.Collections.Hashtable" -and $_.PrivateData.PSData["Prerelease"]) { $modInfoPrerelease = $_.PrivateData.PSData.Prerelease } (-not $RequiredVersion) -or (($reqVersion -eq $_.Version) -and ($reqPrerelease -match $modInfoPrerelease)) } if (-not $module) { if ($RequiredVersion) { $message = $LocalizedData.ModuleWithRequiredVersionNotAvailableLocally -f ($Name, $RequiredVersion) } else { $message = $LocalizedData.ModuleNotAvailableLocally -f ($Name) } ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "ModuleNotAvailableLocallyToPublish" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Name } elseif ($module.GetType().ToString() -ne "System.Management.Automation.PSModuleInfo") { $message = $LocalizedData.AmbiguousModuleName -f ($Name) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId "AmbiguousModuleNameToPublish" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Name } $moduleName = $module.Name $Path = $module.ModuleBase } else { $resolvedPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore if (-not $resolvedPath -or -not (Microsoft.PowerShell.Management\Test-Path -Path $resolvedPath -PathType Container)) { ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage ($LocalizedData.PathIsNotADirectory -f ($Path)) -ErrorId "PathIsNotADirectory" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Path return } $moduleName = Microsoft.PowerShell.Management\Split-Path -Path $resolvedPath -Leaf $modulePathWithVersion = $false [Version]$ModuleVersion = $null if ([System.Version]::TryParse($moduleName, ([ref]$ModuleVersion))) { $moduleName = Microsoft.PowerShell.Management\Split-Path -Path (Microsoft.PowerShell.Management\Split-Path $resolvedPath -Parent) -Leaf $modulePathWithVersion = $true } $manifestPath = Join-PathUtility -Path $resolvedPath -ChildPath "$moduleName.psd1" -PathType File $module = $null if (Microsoft.PowerShell.Management\Test-Path -Path $manifestPath -PathType Leaf) { $ev = $null $module = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $manifestPath -ErrorVariable ev -Verbose:$VerbosePreference if ($ev) { return } } elseif (-not $modulePathWithVersion -and ($PSVersionTable.PSVersion -ge '5.0.0')) { $module = Microsoft.PowerShell.Core\Get-Module -Name $resolvedPath -ListAvailable -ErrorAction SilentlyContinue -Verbose:$false } if (-not $module) { $message = $LocalizedData.InvalidModulePathToPublish -f ($Path) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId 'InvalidModulePathToPublish' -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Path } elseif ($module.GetType().ToString() -ne "System.Management.Automation.PSModuleInfo") { $message = $LocalizedData.AmbiguousModulePath -f ($Path) ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $message -ErrorId 'AmbiguousModulePathToPublish' -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidArgument -ExceptionObject $Path } if ($module -and (-not $module.Path.EndsWith('.psd1', [System.StringComparison]::OrdinalIgnoreCase))) { $message = $LocalizedData.InvalidModuleToPublish -f ($module.Name) ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "InvalidModuleToPublish" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation -ExceptionObject $module.Name } $moduleName = $module.Name $Path = $module.ModuleBase } $message = $LocalizedData.PublishModuleLocation -f ($moduleName, $Path) Write-Verbose -Message $message if ($Tags) { $message = $LocalizedData.TagsShouldBeIncludedInManifestFile -f ($moduleName, $Path) Write-Warning $message } if ($ReleaseNotes) { $message = $LocalizedData.ReleaseNotesShouldBeIncludedInManifestFile -f ($moduleName, $Path) Write-Warning $message } if ($LicenseUri) { $message = $LocalizedData.LicenseUriShouldBeIncludedInManifestFile -f ($moduleName, $Path) Write-Warning $message } if ($IconUri) { $message = $LocalizedData.IconUriShouldBeIncludedInManifestFile -f ($moduleName, $Path) Write-Warning $message } if ($ProjectUri) { $message = $LocalizedData.ProjectUriShouldBeIncludedInManifestFile -f ($moduleName, $Path) Write-Warning $message } $tempModulePath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath -ChildPath "$(Microsoft.PowerShell.Utility\Get-Random)\$moduleName" if ($FormatVersion -eq "1.0") { $tempModulePathForFormatVersion = Microsoft.PowerShell.Management\Join-Path $tempModulePath "Content\Deployment\$script:ModuleReferences\$moduleName" } else { $tempModulePathForFormatVersion = $tempModulePath } $null = Microsoft.PowerShell.Management\New-Item -Path $tempModulePathForFormatVersion -ItemType Directory -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false Microsoft.PowerShell.Management\Get-ChildItem $Path -recurse | Microsoft.PowerShell.Management\Copy-Item -Force -Confirm:$false -WhatIf:$false -Destination { if ($_.PSIsContainer) { Join-Path $tempModulePathForFormatVersion $_.Parent.FullName.substring($path.length) } else { Join-Path $tempModulePathForFormatVersion $_.FullName.Substring($path.Length) } } try { $manifestPath = Join-PathUtility -Path $tempModulePathForFormatVersion -ChildPath "$moduleName.psd1" -PathType File if (-not (Microsoft.PowerShell.Management\Test-Path $manifestPath)) { $message = $LocalizedData.InvalidModuleToPublish -f ($moduleName) ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "InvalidModuleToPublish" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation -ExceptionObject $moduleName } $ev = $null $moduleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $manifestPath -ErrorVariable ev -Verbose:$VerbosePreference if ($ev) { return } if (-not $moduleInfo -or -not $moduleInfo.Author -or -not $moduleInfo.Description) { $message = $LocalizedData.MissingRequiredManifestKeys -f ($moduleName) ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "MissingRequiredModuleManifestKeys" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation -ExceptionObject $moduleName } $moduleInfoPrerelease = $null if ($moduleInfo.PrivateData -and $moduleInfo.PrivateData.GetType().ToString() -eq "System.Collections.Hashtable" -and $moduleInfo.PrivateData["PSData"] -and $moduleInfo.PrivateData.PSData.GetType().ToString() -eq "System.Collections.Hashtable" -and $moduleInfo.PrivateData.PSData["Prerelease"]) { $moduleInfoPrerelease = $moduleInfo.PrivateData.PSData.Prerelease } $result = ValidateAndGet-VersionPrereleaseStrings -Version $moduleInfo.Version -Prerelease $moduleInfoPrerelease -CallerPSCmdlet $PSCmdlet if (-not $result) { return } $moduleInfoVersion = $result["Version"] $moduleInfoPrerelease = $result["Prerelease"] $moduleInfoFullVersion = $result["FullVersion"] $FindParameters = @{Name = $moduleName Repository = $Repository Tag = 'PSScript' AllowPrerelease = $true Verbose = $VerbosePreference ErrorAction = 'SilentlyContinue' WarningAction = 'SilentlyContinue' Debug = $DebugPreference } if ($Credential) { $FindParameters[$script:Credential] = $Credential } $scriptPSGetItemInfo = Find-Script @FindParameters | Microsoft.PowerShell.Core\Where-Object { $_.Name -eq $moduleName } | Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore if ($scriptPSGetItemInfo) { $message = $LocalizedData.SpecifiedNameIsAlearyUsed -f ($moduleName, $Repository, 'Find-Script') ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "SpecifiedNameIsAlearyUsed" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation -ExceptionObject $moduleName } $null = $FindParameters.Remove('Tag') $currentPSGetItemInfo = Find-Module @FindParameters | Microsoft.PowerShell.Core\Where-Object { $_.Name -eq $moduleInfo.Name } | Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore if ($currentPSGetItemInfo) { $result = ValidateAndGet-VersionPrereleaseStrings -Version $currentPSGetItemInfo.Version -CallerPSCmdlet $PSCmdlet if (-not $result) { return } $currentPSGetItemVersion = $result["Version"] $currentPSGetItemPrereleaseString = $result["Prerelease"] $currentPSGetItemFullVersion = $result["FullVersion"] if ($currentPSGetItemVersion -eq $moduleInfoVersion) { if (-not $currentPSGetItemPrereleaseString -and -not $moduleInfoPrerelease) { $message = $LocalizedData.ModuleVersionIsAlreadyAvailableInTheGallery -f ($moduleInfo.Name, $moduleInfoFullVersion, $currentPSGetItemFullVersion, $currentPSGetItemInfo.RepositorySourceLocation) ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $message -ErrorId 'ModuleVersionIsAlreadyAvailableInTheGallery' -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation } elseif (-not $Force -and (-not $currentPSGetItemPrereleaseString -and $moduleInfoPrerelease)) { $message = $LocalizedData.ModuleVersionShouldBeGreaterThanGalleryVersion -f ($moduleInfo.Name, $moduleInfoFullVersion, $currentPSGetItemFullVersion, $currentPSGetItemInfo.RepositorySourceLocation) ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "ModuleVersionShouldBeGreaterThanGalleryVersion" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation } elseif ($currentPSGetItemPrereleaseString -and $moduleInfoPrerelease) { if ($currentPSGetItemPrereleaseString -eq $moduleInfoPrerelease) { $message = $LocalizedData.ModuleVersionIsAlreadyAvailableInTheGallery -f ($moduleInfo.Name, $moduleInfoFullVersion, $currentPSGetItemFullVersion, $currentPSGetItemInfo.RepositorySourceLocation) ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $message -ErrorId 'ModuleVersionIsAlreadyAvailableInTheGallery' -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation } elseif (-not $Force -and ($currentPSGetItemPrereleaseString -gt $moduleInfoPrerelease)) { $message = $LocalizedData.ModuleVersionShouldBeGreaterThanGalleryVersion -f ($moduleInfo.Name, $moduleInfoFullVersion, $currentPSGetItemFullVersion, $currentPSGetItemInfo.RepositorySourceLocation) ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "ModuleVersionShouldBeGreaterThanGalleryVersion" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation } } } elseif (-not $Force -and (Compare-PrereleaseVersions -FirstItemVersion $moduleInfoVersion -FirstItemPrerelease $moduleInfoPrerelease -SecondItemVersion $currentPSGetItemVersion -SecondItemPrerelease $currentPSGetItemPrereleaseString)) { $message = $LocalizedData.ModuleVersionShouldBeGreaterThanGalleryVersion -f ($moduleInfo.Name, $moduleInfoVersion, $currentPSGetItemFullVersion, $currentPSGetItemInfo.RepositorySourceLocation) ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $message -ErrorId "ModuleVersionShouldBeGreaterThanGalleryVersion" -CallerPSCmdlet $PSCmdlet -ErrorCategory InvalidOperation } } $shouldProcessMessage = $LocalizedData.PublishModulewhatIfMessage -f ($moduleInfo.Version, $moduleInfo.Name) if ($Force -or $PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Module")) { $PublishPSArtifactUtility_Params = @{PSModuleInfo = $moduleInfo ManifestPath = $manifestPath NugetApiKey = $NuGetApiKey Destination = $DestinationLocation Repository = $Repository NugetPackageRoot = $tempModulePath FormatVersion = $FormatVersion ReleaseNotes = $($ReleaseNotes -join "`r`n") Tags = $Tags SkipAutomaticTags = $SkipAutomaticTags LicenseUri = $LicenseUri IconUri = $IconUri ProjectUri = $ProjectUri Verbose = $VerbosePreference WarningAction = $WarningPreference ErrorAction = $ErrorActionPreference Debug = $DebugPreference } if ($PSBoundParameters.Containskey('Credential')) { $PublishPSArtifactUtility_Params.Add('Credential', $Credential) } if ($Exclude) { $PublishPSArtifactUtility_Params.Add('Exclude', $Exclude) } Publish-PSArtifactUtility @PublishPSArtifactUtility_Params } } finally { Microsoft.PowerShell.Management\Remove-Item $tempModulePath -Force -Recurse -ErrorAction Ignore -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false } } } function Update-MarkdownHelpModule { [CmdletBinding()] [OutputType([System.IO.FileInfo[]])] param([Parameter(Mandatory = $true, ValueFromPipeline = $true)] [SupportsWildcards()] [string[]]$Path, [System.Text.Encoding]$Encoding = $script:UTF8_NO_BOM, [switch]$RefreshModulePage, [string]$ModulePagePath, [string]$LogPath, [switch]$LogAppend, [switch]$AlphabeticParamsOrder, [switch]$UseFullTypeName, [switch]$UpdateInputOutput, [switch]$Force, [System.Management.Automation.Runspaces.PSSession]$Session, [switch]$ExcludeDontShow) begin { validateWorkingProvider $infoCallback = GetInfoCallback $LogPath -Append:$LogAppend $MarkdownFiles = @() } process { } end { function log { param([string]$message, [switch]$warning) $message = "[Update-MarkdownHelpModule] $([datetime]::now) $message" if ($warning) { Write-Warning $message } $infoCallback.Invoke($message) } foreach ($modulePath in $Path) { $module = $null $h = Get-MarkdownMetadata -Path $modulePath if ($h.$script:MODULE_PAGE_MODULE_NAME) { $module = $h.$script:MODULE_PAGE_MODULE_NAME | Select-Object -First 1 log ($LocalizedData.ModuleNameFromPath -f $modulePath, $module) } if (-not $module) { Write-Error -Message ($LocalizedData.ModuleNameNotFoundFromPath -f $modulePath) continue } log ("[Update-MarkdownHelpModule]" + (Get-Date).ToString()) log ($LocalizedData.UpdateDocsForModule -f $module, $modulePath) $affectedFiles = Update-MarkdownHelp -Session $Session -Path $modulePath -LogPath $LogPath -LogAppend -Encoding $Encoding -AlphabeticParamsOrder:$AlphabeticParamsOrder -UseFullTypeName:$UseFullTypeName -UpdateInputOutput:$UpdateInputOutput -Force:$Force -ExcludeDontShow:$ExcludeDontShow $affectedFiles $allCommands = GetCommands -AsNames -Module $Module if (-not $allCommands) { throw $LocalizedData.ModuleOrCommandNotFound -f $Module } $updatedCommands = $affectedFiles.BaseName $allCommands | ForEach-Object { if (-not ($updatedCommands -contains $_)) { log ($LocalizedData.CreatingNewMarkdownForCommand -f $_) $newFiles = New-MarkdownHelp -Command $_ -OutputFolder $modulePath -AlphabeticParamsOrder:$AlphabeticParamsOrder -Force:$Force -ExcludeDontShow:$ExcludeDontShow $newFiles } } if ($RefreshModulePage) { $MamlModel = New-Object System.Collections.Generic.List[Markdown.MAML.Model.MAML.MamlCommand] $files = @() $MamlModel = GetMamlModelImpl $affectedFiles -ForAnotherMarkdown -Encoding $Encoding NewModuleLandingPage -RefreshModulePage -ModulePagePath $ModulePagePath -Path $modulePath -ModuleName $module -Module $MamlModel -Encoding $Encoding -Force } } } } function Find-Module { <# .ExternalHelp PSModule-help.xml #> [CmdletBinding(HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=398574')] [outputtype("PSCustomObject[]")] Param ([Parameter(ValueFromPipelineByPropertyName = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string] $MinimumVersion, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string] $MaximumVersion, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string] $RequiredVersion, [Parameter()] [switch] $AllVersions, [Parameter()] [switch] $IncludeDependencies, [Parameter()] [ValidateNotNull()] [string] $Filter, [Parameter()] [ValidateNotNull()] [string[]] $Tag, [Parameter()] [ValidateNotNull()] [ValidateSet('DscResource', 'Cmdlet', 'Function', 'RoleCapability')] [string[]] $Includes, [Parameter()] [ValidateNotNull()] [string[]] $DscResource, [Parameter()] [ValidateNotNull()] [string[]] $RoleCapability, [Parameter()] [ValidateNotNull()] [string[]] $Command, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Uri] $Proxy, [Parameter(ValueFromPipelineByPropertyName = $true)] [PSCredential] $ProxyCredential, [Parameter()] [ValidateNotNullOrEmpty()] [string[]] $Repository, [Parameter(ValueFromPipelineByPropertyName = $true)] [PSCredential] $Credential, [Parameter()] [switch] $AllowPrerelease) Begin { Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential } Process { $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet -Name $Name -MinimumVersion $MinimumVersion -MaximumVersion $MaximumVersion -RequiredVersion $RequiredVersion -AllVersions:$AllVersions -AllowPrerelease:$AllowPrerelease if (-not $ValidationResult) { return } $PSBoundParameters["Provider"] = $script:PSModuleProviderName $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeModule if ($AllowPrerelease) { $PSBoundParameters[$script:AllowPrereleaseVersions] = $true } $null = $PSBoundParameters.Remove("AllowPrerelease") if ($PSBoundParameters.ContainsKey("Repository")) { $PSBoundParameters["Source"] = $Repository $null = $PSBoundParameters.Remove("Repository") $ev = $null $null = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false if ($ev) { return } } $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock $modulesFoundInPSGallery = @() $isRepositoryNullOrPSGallerySpecified = $false if ($Repository -and ($Repository -Contains $Script:PSGalleryModuleSource)) { $isRepositoryNullOrPSGallerySpecified = $true } elseif (-not $Repository) { $psgalleryRepo = Get-PSRepository -Name $Script:PSGalleryModuleSource -ErrorAction SilentlyContinue -WarningAction SilentlyContinue if ($psgalleryRepo) { $isRepositoryNullOrPSGallerySpecified = $true } } PackageManagement\Find-Package @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object { $psgetItemInfo = New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeModule if ($psgetItemInfo.Type -eq $script:PSArtifactTypeModule) { if ($AllVersions -and -not $AllowPrerelease) { if ($psgetItemInfo.AdditionalMetadata -and $psgetItemInfo.AdditionalMetadata.IsPrerelease -eq 'false') { $psgetItemInfo } } else { $psgetItemInfo } } elseif ($PSBoundParameters['Name'] -and -not (Test-WildcardPattern -Name ($Name | Microsoft.PowerShell.Core\Where-Object { $psgetItemInfo.Name -like $_ }))) { $message = $LocalizedData.MatchInvalidType -f ($psgetItemInfo.Name, $psgetItemInfo.Type, $script:PSArtifactTypeModule) Write-Error -Message $message -ErrorId 'MatchInvalidType' -Category InvalidArgument -TargetObject $Name } if ($psgetItemInfo -and $isRepositoryNullOrPSGallerySpecified -and $script:TelemetryEnabled -and ($psgetItemInfo.Repository -eq $Script:PSGalleryModuleSource)) { $modulesFoundInPSGallery += $psgetItemInfo.Name } } if ($isRepositoryNullOrPSGallerySpecified) { Log-ArtifactNotFoundInPSGallery -SearchedName $Name -FoundName $modulesFoundInPSGallery -operationName 'PSGET_FIND_MODULE' } } } function Find-Script { <# .ExternalHelp PSModule-help.xml #> [CmdletBinding(HelpUri = 'https://go.microsoft.com/fwlink/?LinkId=619785')] [outputtype("PSCustomObject[]")] Param ([Parameter(ValueFromPipelineByPropertyName = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string] $MinimumVersion, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string] $MaximumVersion, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [string] $RequiredVersion, [Parameter()] [switch] $AllVersions, [Parameter()] [switch] $IncludeDependencies, [Parameter()] [ValidateNotNull()] [string] $Filter, [Parameter()] [ValidateNotNull()] [string[]] $Tag, [Parameter()] [ValidateNotNull()] [ValidateSet('Function', 'Workflow')] [string[]] $Includes, [Parameter()] [ValidateNotNull()] [string[]] $Command, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Uri] $Proxy, [Parameter(ValueFromPipelineByPropertyName = $true)] [PSCredential] $ProxyCredential, [Parameter()] [ValidateNotNullOrEmpty()] [string[]] $Repository, [Parameter(ValueFromPipelineByPropertyName = $true)] [PSCredential] $Credential, [Parameter()] [switch] $AllowPrerelease) Begin { Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential } Process { $ValidationResult = Validate-VersionParameters -CallerPSCmdlet $PSCmdlet -Name $Name -MinimumVersion $MinimumVersion -MaximumVersion $MaximumVersion -RequiredVersion $RequiredVersion -AllVersions:$AllVersions -AllowPrerelease:$AllowPrerelease if (-not $ValidationResult) { return } $PSBoundParameters['Provider'] = $script:PSModuleProviderName $PSBoundParameters[$script:PSArtifactType] = $script:PSArtifactTypeScript if ($AllowPrerelease) { $PSBoundParameters[$script:AllowPrereleaseVersions] = $true } $null = $PSBoundParameters.Remove("AllowPrerelease") if ($PSBoundParameters.ContainsKey("Repository")) { $PSBoundParameters["Source"] = $Repository $null = $PSBoundParameters.Remove("Repository") $ev = $null $repositories = Get-PSRepository -Name $Repository -ErrorVariable ev -verbose:$false if ($ev) { return } $RepositoriesWithoutScriptSourceLocation = $false foreach ($repo in $repositories) { if (-not $repo.ScriptSourceLocation) { $message = $LocalizedData.ScriptSourceLocationIsMissing -f ($repo.Name) Write-Error -Message $message -ErrorId 'ScriptSourceLocationIsMissing' -Category InvalidArgument -TargetObject $repo.Name -Exception 'System.ArgumentException' $RepositoriesWithoutScriptSourceLocation = $true } } if ($RepositoriesWithoutScriptSourceLocation) { return } } $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlockForScriptCmdlets $scriptsFoundInPSGallery = @() $isRepositoryNullOrPSGallerySpecified = $false if ($Repository -and ($Repository -Contains $Script:PSGalleryModuleSource)) { $isRepositoryNullOrPSGallerySpecified = $true } elseif (-not $Repository) { $psgalleryRepo = Get-PSRepository -Name $Script:PSGalleryModuleSource -ErrorAction SilentlyContinue -WarningAction SilentlyContinue if ($psgalleryRepo) { $isRepositoryNullOrPSGallerySpecified = $true } } PackageManagement\Find-Package @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object { $psgetItemInfo = New-PSGetItemInfo -SoftwareIdentity $_ -Type $script:PSArtifactTypeScript if ($psgetItemInfo.Type -eq $script:PSArtifactTypeScript) { if ($AllVersions -and -not $AllowPrerelease) { if ($psgetItemInfo.AdditionalMetadata -and $psgetItemInfo.AdditionalMetadata.IsPrerelease -eq $false) { $psgetItemInfo } } else { $psgetItemInfo } } elseif ($PSBoundParameters['Name'] -and -not (Test-WildcardPattern -Name ($Name | Microsoft.PowerShell.Core\Where-Object { $psgetItemInfo.Name -like $_ }))) { $message = $LocalizedData.MatchInvalidType -f ($psgetItemInfo.Name, $psgetItemInfo.Type, $script:PSArtifactTypeScript) Write-Error -Message $message -ErrorId 'MatchInvalidType' -Category InvalidArgument -TargetObject $Name } if ($psgetItemInfo -and $isRepositoryNullOrPSGallerySpecified -and $script:TelemetryEnabled -and ($psgetItemInfo.Repository -eq $Script:PSGalleryModuleSource)) { $scriptsFoundInPSGallery += $psgetItemInfo.Name } } if ($isRepositoryNullOrPSGallerySpecified) { Log-ArtifactNotFoundInPSGallery -SearchedName $Name -FoundName $scriptsFoundInPSGallery -operationName PSGET_FIND_SCRIPT } } } function Get-MarkdownMetadata { [CmdletBinding(DefaultParameterSetName = "FromPath")] param([Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1, ParameterSetName = "FromPath")] [SupportsWildcards()] [string[]]$Path, [Parameter(Mandatory = $true, ParameterSetName = "FromMarkdownString")] [string]$Markdown) process { if ($PSCmdlet.ParameterSetName -eq 'FromMarkdownString') { return [Markdown.MAML.Parser.MarkdownParser]::GetYamlMetadata($Markdown) } else { GetMarkdownFilesFromPath $Path -IncludeModulePage | ForEach-Object { $md = Get-Content -Raw $_.FullName [Markdown.MAML.Parser.MarkdownParser]::GetYamlMetadata($md) } } } } function Get-PSRepository { <# .ExternalHelp PSModule-help.xml #> [CmdletBinding(HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=517127')] Param ([Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string[]] $Name) Begin { } Process { $PSBoundParameters["Provider"] = $script:PSModuleProviderName $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock if ($Name) { foreach ($sourceName in $Name) { $PSBoundParameters["Name"] = $sourceName $packageSources = PackageManagement\Get-PackageSource @PSBoundParameters $packageSources | Microsoft.PowerShell.Core\ForEach-Object { New-ModuleSourceFromPackageSource -PackageSource $_ } } } else { $packageSources = PackageManagement\Get-PackageSource @PSBoundParameters $packageSources | Microsoft.PowerShell.Core\ForEach-Object { New-ModuleSourceFromPackageSource -PackageSource $_ } } } } function Update-MarkdownHelp { [CmdletBinding()] [OutputType([System.IO.FileInfo[]])] param([Parameter(Mandatory = $true, ValueFromPipeline = $true)] [SupportsWildcards()] [string[]]$Path, [System.Text.Encoding]$Encoding = $script:UTF8_NO_BOM, [string]$LogPath, [switch]$LogAppend, [switch]$AlphabeticParamsOrder, [switch]$UseFullTypeName, [switch]$UpdateInputOutput, [Switch]$Force, [System.Management.Automation.Runspaces.PSSession]$Session, [switch]$ExcludeDontShow) begin { validateWorkingProvider $infoCallback = GetInfoCallback $LogPath -Append:$LogAppend $MarkdownFiles = @() } process { $MarkdownFiles += GetMarkdownFilesFromPath $Path } end { function log { param([string]$message, [switch]$warning) $message = "[Update-MarkdownHelp] $([datetime]::now) $message" if ($warning) { Write-Warning $message } $infoCallback.Invoke($message) } if (-not $MarkdownFiles) { log -warning ($LocalizedData.NoMarkdownFiles -f $Path) return } $MarkdownFiles | ForEach-Object { $file = $_ $filePath = $file.FullName $oldModels = GetMamlModelImpl $filePath -ForAnotherMarkdown -Encoding $Encoding if ($oldModels.Count -gt 1) { log -warning ($LocalizedData.FileContainsMoreThanOneCommand -f $filePath) log -warning $LocalizedData.OneCommandPerFile return } $oldModel = $oldModels[0] $name = $oldModel.Name [Array]$loadedModulesBefore = $(Get-Module | Select-Object -Property Name) $command = Get-Command $name -ErrorAction SilentlyContinue if (-not $command) { if ($Force) { if (Test-Path $filePath) { Remove-Item -Path $filePath -Confirm:$false log -warning ($LocalizedData.CommandNotFoundFileRemoved -f $name, $filePath) return } } else { log -warning ($LocalizedData.CommandNotFoundSkippingFile -f $name, $filePath) return } } elseif (($null -ne $command.ModuleName) -and ($loadedModulesBefore.Name -notcontains $command.ModuleName)) { log -warning ($LocalizedData.ModuleImporteAutomaticaly -f $($command.ModuleName)) } $metadata = Get-MarkdownMetadata $filePath $metadata["external help file"] = GetHelpFileName $command $reflectionModel = GetMamlObject -Session $Session -Cmdlet $name -UseFullTypeName:$UseFullTypeName -ExcludeDontShow:$ExcludeDontShow.IsPresent $metadata[$script:MODULE_PAGE_MODULE_NAME] = $reflectionModel.ModuleName $merger = New-Object Markdown.MAML.Transformer.MamlModelMerger -ArgumentList $infoCallback $newModel = $merger.Merge($reflectionModel, $oldModel, $UpdateInputOutput) if ($AlphabeticParamsOrder) { SortParamsAlphabetically $newModel } $md = ConvertMamlModelToMarkdown -mamlCommand $newModel -metadata $metadata -PreserveFormatting MySetContent -path $file.FullName -value $md -Encoding $Encoding -Force } } } function Add-Directory { [CmdletBinding()] param([string] $Directory) $exists = Test-Path -Path $Directory if ($exists -eq $false) { $null = New-Item -Path $Directory -ItemType Directory -Force } } function Add-FilesWithFolders { [CmdletBinding()] param ($file, $FullProjectPath, $directory) $LinkPrivatePublicFiles = foreach ($dir in $directory) { if ($file -like "$dir*") { $file } } $LinkPrivatePublicFiles } 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 } $EnumsList = @($Files = Foreach ($import in @($Enums)) { "'Enums\$($import.Name)'" } $Files -join ',') return [string] "@($EnumsList)" } function Find-RequiredModules { param([string] $Name) $Module = Get-Module -ListAvailable $Name -ErrorAction SilentlyContinue -Verbose:$false $AllModules = if ($Module) { [Array] $RequiredModules = $Module.RequiredModules.Name if ($null -ne $RequiredModules) { $null } $RequiredModules foreach ($_ in $RequiredModules) { Find-RequiredModules -Path $Path -Name $_ } } [Array] $ListModules = $AllModules | Where-Object { $null -ne $_ } if ($null -ne $ListModules) { [array]::Reverse($ListModules) } $CleanedModules = [System.Collections.Generic.List[string]]::new() foreach ($_ in $ListModules) { if ($CleanedModules -notcontains $_) { $CleanedModules.Add($_) } } $CleanedModules } function Format-Code { [cmdletbinding()] param([string] $FilePath, $FormatCode) if ($FormatCode.Enabled) { if ($FormatCode.RemoveComments) { $Output = Write-TextWithTime -Text "[+] Removing Comments - $FilePath" { Remove-Comments -FilePath $FilePath } } else { $Output = Write-TextWithTime -Text "[+] Reading file content - $FilePath" { Get-Content -LiteralPath $FilePath -Raw } } if ($null -eq $FormatCode.FormatterSettings) { $FormatCode.FormatterSettings = $Script:FormatterSettings } $Data = Write-TextWithTime -Text "[+] Formatting file - $FilePath" { try { Invoke-Formatter -ScriptDefinition $Output -Settings $FormatCode.FormatterSettings -Verbose:$false } catch { $ErrorMessage = $_.Exception.Message Write-Error "Format-Code - Formatting on file $FilePath failed. Error: $ErrorMessage" Exit } } Write-TextWithTime -Text "[+] Saving file - $FilePath" { $Final = foreach ($O in $Data) { if ($O.Trim() -ne '') { $O.Trim() } } try { $Final | Out-File -LiteralPath $FilePath -NoNewline -Encoding utf8 } catch { $ErrorMessage = $_.Exception.Message Write-Text "[-] Format-Code - Resaving file $FilePath failed. Error: $ErrorMessage" -Color Red Exit } } } } 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 } if ($FilePath -ne '' -and (Test-Path -Path $FilePath) -and (Get-Item -LiteralPath $FilePath).Length -gt 0kb) { $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() $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 $FilePathSave -Encoding utf8 return $true } else { $Content | Add-Content -LiteralPath $FilePathSave -Encoding utf8 return $False } } } Function Get-AliasTarget { [cmdletbinding()] param ([Alias('PSPath', 'FullName')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]]$Path, [string] $Content, [switch] $RecurseFunctionNames) process { if ($Content) { $ProcessData = $Content $Code = $true } else { $ProcessData = $Path $Code = $false } foreach ($File in $ProcessData) { $Ast = $null if ($Code) { $FileAst = [System.Management.Automation.Language.Parser]::ParseInput($File, [ref]$null, [ref]$null) } else { $FileAst = [System.Management.Automation.Language.Parser]::ParseFile($File , [ref]$null, [ref]$null) } $FunctionName = $FileAst.FindAll( { param ($ast) $ast -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $RecurseFunctionNames).Name $Ast = $Null $AliasDefinitions = $FileAst.FindAll( { param ($ast) $ast -is [System.Management.Automation.Language.AttributeAst] -and $ast.TypeName.Name -eq 'Alias' -and $ast.Parent -is [System.Management.Automation.Language.ParamBlockAst] }, $true) $AliasTarget = @($AliasDefinitions.PositionalArguments.Value foreach ($_ in $AliasDefinitions.Parent.CommandElements) { if ($_.StringConstantType -eq 'BareWord' -and $_.Value -notin ('New-Alias', 'Set-Alias', $FunctionName)) { $_.Value } }) $AliasTarget = foreach ($_ in $AliasTarget) { if ($_ -ne $null) { $_ } } [PsCustomObject]@{Function = $FunctionName Alias = $AliasTarget } } } } function Get-FilteredScriptCommands { [CmdletBinding()] param([Array] $Commands, [switch] $NotCmdlet, [switch] $NotUnknown, [switch] $NotApplication, [string[]] $Functions) if ($Functions.Count -eq 0) { $Functions = Get-FunctionNames -Path $FilePath } $Commands = $Commands | Where-Object { $_ -notin $Functions } $Commands = $Commands | Sort-Object -Unique $Scan = foreach ($Command in $Commands) { try { $Data = Get-Command -Name $Command -ErrorAction Stop [PSCustomObject] @{Name = $Data.Name Source = $Data.Source CommandType = $Data.CommandType Error = '' ScriptBlock = $Data.ScriptBlock } } catch { [PSCustomObject] @{Name = $Command Source = '' CommandType = '' Error = $_.Exception.Message ScriptBlock = '' } } } $Filtered = foreach ($Command in $Scan) { if ($NotCmdlet -and $NotUnknown -and $NotApplication) { if ($Command.CommandType -ne 'Cmdlet' -and $Command.Source -ne '' -and $Command.CommandType -ne 'Application') { $Command } } elseif ($NotCmdlet -and $NotUnknown) { if ($Command.CommandType -ne 'Cmdlet' -and $Command.Source -ne '') { $Command } } elseif ($NotCmdlet) { if ($Command.CommandType -ne 'Cmdlet') { $Command } } elseif ($NotUnknown) { if ($Command.Source -ne '') { $Command } } elseif ($NotApplication) { if ($Command.CommandType -ne 'Application') { $Command } } else { $Command } } $Filtered } function Get-FunctionAliasesFromFolder { [cmdletbinding()] param([string] $FullProjectPath, [string[]] $Folder, [Array] $Files) $FilesPS1 = foreach ($File in $Files) { if ($file.FullName -like "*\Public\*") { if ($File.Extension -eq '.ps1' -or $File.Extension -eq '*.psm1') { $File } } } [Array] $Content = foreach ($File in $FilesPS1) { '' Get-Content -LiteralPath $File.FullName -Raw -Encoding Default } $Code = $Content -join [System.Environment]::NewLine $AliasesToExport = Get-AliasTarget -Content $Code $AliasesToExport } function Get-FunctionNames { [cmdletbinding()] param([string] $Path, [switch] $Recurse) [System.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-GitLog { [CmdLetBinding(DefaultParameterSetName = 'Default')] param ([Parameter(ParameterSetName = 'Default', Mandatory)] [Parameter(ParameterSetName = 'SourceTarget', Mandatory)] [ValidateScript( { Resolve-Path -Path $_ | Test-Path })] [string]$GitFolder, [Parameter(ParameterSetName = 'SourceTarget', Mandatory)] [string]$StartCommitId, [Parameter(ParameterSetName = 'SourceTarget')] [string]$EndCommitId = 'HEAD') Push-Location try { Set-Location -Path $GitFolder $GitCommand = Get-Command -Name git -ErrorAction Stop } catch { $PSCmdlet.ThrowTerminatingError($_) } if ($StartCommitId) { $GitLogCommand = '"{0}" log --oneline --format="%H`t%h`t%ai`t%an`t%ae`t%ci`t%cn`t%ce`t%s`t%f" {1}...{2} 2>&1' -f $GitCommand.Source, $StartCommitId, $EndCommitId } else { $GitLogCommand = '"{0}" log --oneline --format="%H`t%h`t%ai`t%an`t%ae`t%ci`t%cn`t%ce`t%s`t%f" 2>&1' -f $GitCommand.Source } Write-Verbose -Message $GitLogCommand $GitLog = Invoke-Expression -Command "& $GitLogCommand" -ErrorAction SilentlyContinue Pop-Location if ($GitLog[0] -notmatch 'fatal:') { $GitLog | ConvertFrom-Csv -Delimiter "`t" -Header 'CommitId', 'ShortCommitId', 'AuthorDate', 'AuthorName', 'AuthorEmail', 'CommitterDate', 'CommitterName', 'ComitterEmail', 'CommitMessage', 'SafeCommitMessage' } else { if ($GitLog[0] -like "fatal: ambiguous argument '*...*'*") { Write-Warning -Message 'Unknown revision. Please check the values for StartCommitId or EndCommitId; omit the parameters to retrieve the entire log.' } else { Write-Error -Category InvalidArgument -Message ($GitLog -join "`n") } } } function Get-MissingFunctions { [CmdletBinding()] param([alias('Path')][string] $FilePath, [string[]] $Functions, [switch] $Summary, [switch] $SummaryWithCommands) $ListCommands = [System.Collections.Generic.List[Object]]::new() $Result = Get-ScriptCommands -FilePath $FilePath -CommandsOnly $FilteredCommands = Get-FilteredScriptCommands -Commands $Result -NotUnknown -NotCmdlet -Functions $Functions -NotApplication foreach ($_ in $FilteredCommands) { $ListCommands.Add($_) } Get-RecursiveCommands -Commands $FilteredCommands $FunctionsOutput = foreach ($_ in $ListCommands) { "function $($_.Name) { $($_.ScriptBlock) }" } if ($SummaryWithCommands) { $Hash = @{Summary = $ListCommands Functions = $FunctionsOutput } return $Hash } elseif ($Summary) { return $ListCommands } else { return $FunctionsOutput } } function Get-RecursiveCommands { [CmdletBinding()] param([Array] $Commands) $Another = foreach ($Command in $Commands) { if ($Command.ScriptBlock) { Get-ScriptCommands -Code $Command.ScriptBlock -CommandsOnly } } $filter = Get-FilteredScriptCommands -Commands $Another -NotUnknown -NotCmdlet [Array] $ProcessedCommands = foreach ($_ in $Filter) { if ($_.Name -notin $ListCommands.Name) { $ListCommands.Add($_) $_ } } if ($ProcessedCommands.Count -gt 0) { Get-RecursiveCommands -Commands $ProcessedCommands } } function Get-ScriptCommands { [cmdletBinding(DefaultParameterSetName = 'File')] param ([alias('Path')][Parameter(ParameterSetName = 'File')][string] $FilePath, [alias('ScriptBlock')][scriptblock] $Code, [switch] $CommandsOnly) begin { $Errors = $null } process { $Errors = $null if ($Code) { $CodeRead = $Code } else { $CodeRead = Get-Content -Path $FilePath -Raw -Encoding Default } $Tokens = [System.Management.Automation.PSParser]::Tokenize($CodeRead, [ref]$Errors) $Commands = foreach ($_ in $Tokens) { if ($_.Type -eq 'Command') { $_ } } if ($CommandsOnly) { $Commands.Content | Sort-Object -Unique } else { $Commands } } } 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, [System.Collections.IDictionary] $FormatCodePSM1, [System.Collections.IDictionary] $FormatCodePSD1, [System.Collections.IDictionary] $Configuration) $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew() Write-Text "[+] 1st stage merging" -Color Blue $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\') try { $Content | Out-File -Append -LiteralPath $PSM1FilePath -Encoding utf8 } catch { $ErrorMessage = $_.Exception.Message Write-Error "Merge-Module - Merge on file $FilePath failed. Error: $ErrorMessage" Exit } } $FilePathUsing = "$ModulePathTarget\$ModuleName.Usings.ps1" $UsingInPlace = Format-UsingNamespace -FilePath $PSM1FilePath -FilePathUsing $FilePathUsing if ($UsingInPlace) { Format-Code -FilePath $FilePathUsing -FormatCode $FormatCodePSM1 $Configuration.UsingInPlace = "$ModuleName.Usings.ps1" } $TimeToExecute.Stop() Write-Text "[+] 1st stage merging [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew() Write-Text "[+] 2nd stage missing functions" -Color Blue $ApprovedModules = $Configuration.Options.Merge.Integrate.ApprovedModules $MissingFunctions = Get-MissingFunctions -FilePath $PSM1FilePath -SummaryWithCommands $TimeToExecute.Stop() Write-Text "[+] 2nd stage missing functions [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew() Write-Text "[+] 3rd stage required modules" -Color Blue $RequiredModules = @(if ($Configuration.Information.Manifest.RequiredModules[0] -is [System.Collections.IDictionary]) { $Configuration.Information.Manifest.RequiredModules.ModuleName } else { $Configuration.Information.Manifest.RequiredModules }) $DependantRequiredModules = foreach ($_ in $RequiredModules) { Find-RequiredModules -Name $_ } $DependantRequiredModules = $DependantRequiredModules | Sort-Object -Unique $TimeToExecute.Stop() Write-Text "[+] 3rd stage required modules [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew() Write-Text "[+] 4th stage commands used" -Color Blue foreach ($Module in $MissingFunctions.Summary.Source | Sort-Object -Unique) { if ($Module -in $RequiredModules -and $Module -in $ApprovedModules) { Write-Text "[+] Module $Module is in required modules with ability to merge." -Color Green $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module }).Name foreach ($F in $MyFunctions) { Write-Text " [>] Command used $F" -Color Yellow } } elseif ($Module -in $DependantRequiredModules -and $Module -in $ApprovedModules) { Write-Text "[+] Module $Module is in dependant required module within required modules with ability to merge." -Color Green $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module }).Name foreach ($F in $MyFunctions) { Write-Text " [>] Command used $F" -Color Yellow } } elseif ($Module -in $DependantRequiredModules) { Write-Text "[+] Module $Module is in dependant required module within required modules." -Color Green $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module }).Name foreach ($F in $MyFunctions) { Write-Text " [>] Command used $F" -Color Green } } elseif ($Module -in $RequiredModules) { Write-Text "[+] Module $Module is in required modules." -Color Green $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module }).Name foreach ($F in $MyFunctions) { Write-Text " [>] Command used $F" -Color Green } } else { Write-Text "[-] Module $Module is missing in required modules. Potential issue." -Color Red $MyFunctions = ($MissingFunctions.Summary | Where-Object { $_.Source -eq $Module }).Name foreach ($F in $MyFunctions) { Write-Text " [>] Command affected $F" -Color Red } } } $TimeToExecute.Stop() Write-Text "[+] 4th stage commands used [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue if ($Configuration.Steps.BuildModule.MergeMissing -eq $true) { $TimeToExecute = [System.Diagnostics.Stopwatch]::StartNew() Write-Text "[+] 5th stage merge mergable commands" -Color Blue $PSM1Content = Get-Content -LiteralPath $PSM1FilePath -Raw $IntegrateContent = @($MissingFunctions.Functions $PSM1Content) $IntegrateContent | Set-Content -LiteralPath $PSM1FilePath -Encoding UTF8 $NewRequiredModules = foreach ($_ in $Configuration.Information.Manifest.RequiredModules) { if ($_ -is [System.Collections.IDictionary]) { if ($_.ModuleName -notin $ApprovedModules) { $_ } } else { if ($_ -notin $ApprovedModules) { $_ } } } $Configuration.Information.Manifest.RequiredModules = $NewRequiredModules $TimeToExecute.Stop() Write-Text "[+] 5th stage merge mergable commands [Time: $($($TimeToExecute.Elapsed).Tostring())]" -Color Blue } 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 -AddUsingsToProcess Format-Code -FilePath $PSD1FilePath -FormatCode $FormatCodePSD1 Get-ChildItem $ModulePathTarget -Recurse -Force -Directory | Sort-Object -Property FullName -Descending | Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } | Remove-Item } function New-CreateModule { [CmdletBinding()] param ([string] $ProjectName, [string] $ModulePath, [string] $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-GitHubRelease { <# .SYNOPSIS Creates a new Release for the given GitHub repository. .DESCRIPTION Uses the GitHub API to create a new Release for a given repository. Allows you to specify all of the Release properties, such as the Tag, Name, Assets, and if it's a Draft or Prerelease or not. .PARAMETER GitHubUsername The username that the GitHub repository exists under. e.g. For the repository https://github.com/deadlydog/New-GitHubRelease, the username is 'deadlydog'. .PARAMETER GitHubRepositoryName The name of the repository to create the Release for. e.g. For the repository https://github.com/deadlydog/New-GitHubRelease, the repository name is 'New-GitHubRelease'. .PARAMETER GitHubAccessToken The Access Token to use as credentials for GitHub. Access tokens can be generated at https://github.com/settings/tokens. The access token will need to have the repo/public_repo permission on it for it to be allowed to create a new Release. .PARAMETER TagName The name of the tag to create at the Commitish. .PARAMETER ReleaseName The name to use for the new release. If blank, the TagName will be used. .PARAMETER ReleaseNotes The text describing the contents of the release. .PARAMETER AssetFilePaths The full paths of the files to include in the release. .PARAMETER Commitish Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists. Default: the repository's default branch (usually master). .PARAMETER IsDraft True to create a draft (unpublished) release, false to create a published one. Default: false .PARAMETER IsPreRelease True to identify the release as a prerelease. false to identify the release as a full release. Default: false .OUTPUTS A hash table with the following properties is returned: Succeeded = $true if the Release was created successfully and all assets were uploaded to it, $false if some part of the process failed. ReleaseCreationSucceeded = $true if the Release was created successfully (does not include asset uploads), $false if the Release was not created. AllAssetUploadsSucceeded = $true if all assets were uploaded to the Release successfully, $false if one of them failed, $null if there were no assets to upload. ReleaseUrl = The URL of the new Release that was created. ErrorMessage = A message describing what went wrong in the case that Succeeded is $false. .EXAMPLE # Import the module dynamically from the PowerShell Gallery. Use CurrentUser scope to avoid having to run as admin. Import-Module -Name New-GitHubRelease -Scope CurrentUser # Specify the parameters required to create the release. Do it as a hash table for easier readability. $newGitHubReleaseParameters = @{ GitHubUsername = 'deadlydog' GitHubRepositoryName = 'New-GitHubRelease' GitHubAccessToken = 'SomeLongHexidecimalString' ReleaseName = "New-GitHubRelease v1.0.0" TagName = "v1.0.0" ReleaseNotes = "This release contains the following changes: ..." AssetFilePaths = @('C:\MyProject\Installer.exe','C:\MyProject\Documentation.md') IsPreRelease = $false IsDraft = $true # Set to true when testing so we don't publish a real release (visible to everyone) by accident. } # Try to create the Release on GitHub and save the results. $result = New-GitHubRelease @newGitHubReleaseParameters # Provide some feedback to the user based on the results. if ($result.Succeeded -eq $true) { Write-Output "Release published successfully! View it at $($result.ReleaseUrl)" } elseif ($result.ReleaseCreationSucceeded -eq $false) { Write-Error "The release was not created. Error message is: $($result.ErrorMessage)" } elseif ($result.AllAssetUploadsSucceeded -eq $false) { Write-Error "The release was created, but not all of the assets were uploaded to it. View it at $($result.ReleaseUrl). Error message is: $($result.ErrorMessage)" } Attempt to create a new Release on GitHub, and provide feedback to the user indicating if it succeeded or not. .LINK Project home: https://github.com/deadlydog/New-GitHubRelease .NOTES Name: New-GitHubRelease Author: Daniel Schroeder (originally based on the script at https://github.com/majkinetor/au/blob/master/scripts/Github-CreateRelease.ps1) GitHub Release API Documentation: https://developer.github.com/v3/repos/releases/#create-a-release Version: 1.0.2 #> [CmdletBinding()] param ([Parameter(Mandatory = $true, HelpMessage = "The username the repository is under (e.g. deadlydog).")] [string] $GitHubUsername, [Parameter(Mandatory = $true, HelpMessage = "The repository name to create the release in (e.g. Invoke-MsBuild).")] [string] $GitHubRepositoryName, [Parameter(Mandatory = $true, HelpMessage = "The Acess Token to use as credentials for GitHub.")] [string] $GitHubAccessToken, [Parameter(Mandatory = $true, HelpMessage = "The name of the tag to create at the the Commitish.")] [string] $TagName, [Parameter(Mandatory = $false, HelpMessage = "The name of the release. If blank, the TagName will be used.")] [string] $ReleaseName, [Parameter(Mandatory = $false, HelpMessage = "Text describing the contents of the tag.")] [string] $ReleaseNotes, [Parameter(Mandatory = $false, HelpMessage = "The full paths of the files to include in the release.")] [string[]] $AssetFilePaths, [Parameter(Mandatory = $false, HelpMessage = "Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists. Default: the repository's default branch (usually master).")] [string] $Commitish, [Parameter(Mandatory = $false, HelpMessage = "True to create a draft (unpublished) release, false to create a published one. Default: false")] [bool] $IsDraft = $false, [Parameter(Mandatory = $false, HelpMessage = "True to identify the release as a prerelease. false to identify the release as a full release. Default: false")] [bool] $IsPreRelease = $false) BEGIN { Set-StrictMode -Version Latest Set-SecurityProtocolForThread [string] $NewLine = [Environment]::NewLine if ([string]::IsNullOrEmpty($ReleaseName)) { $ReleaseName = $TagName } Test-AllFilePathsAndThrowErrorIfOneIsNotValid $AssetFilePaths } END { } PROCESS { $result = @{ } $result.Succeeded = $false $result.ReleaseCreationSucceeded = $false $result.AllAssetUploadsSucceeded = $false $result.ReleaseUrl = $null $result.ErrorMessage = $null [bool] $thereAreNoAssetsToIncludeInTheRelease = ($AssetFilePaths -eq $null) -or ($AssetFilePaths.Count -le 0) if ($thereAreNoAssetsToIncludeInTheRelease) { $result.AllAssetUploadsSucceeded = $null } $authHeader = @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($GitHubAccessToken + ":x-oauth-basic")) } $releaseData = @{tag_name = $TagName target_commitish = $Commitish name = $ReleaseName body = $ReleaseNotes draft = $IsDraft prerelease = $IsPreRelease } $createReleaseWebRequestParameters = @{Uri = "https://api.github.com/repos/$GitHubUsername/$GitHubRepositoryName/releases" Method = 'POST' Headers = $authHeader ContentType = 'application/json' Body = (ConvertTo-Json $releaseData -Compress) } try { Write-Verbose "Sending web request to create the new Release..." $createReleaseWebRequestResults = Invoke-RestMethodAndThrowDescriptiveErrorOnFailure $createReleaseWebRequestParameters } catch { $result.ReleaseCreationSucceeded = $false $result.ErrorMessage = $_.Exception.Message return $result } $result.ReleaseCreationSucceeded = $true $result.ReleaseUrl = $createReleaseWebRequestResults.html_url if ($thereAreNoAssetsToIncludeInTheRelease) { $result.Succeeded = $true return $result } [string] $urlToUploadFilesTo = $createReleaseWebRequestResults.upload_url -replace '{.+}' try { Write-Verbose "Uploading asset files to the new release..." Send-FilesToGitHubRelease -filePathsToUpload $AssetFilePaths -urlToUploadFilesTo $urlToUploadFilesTo -authHeader $authHeader } catch { $result.AllAssetUploadsSucceeded = $false $result.ErrorMessage = $_.Exception.Message return $result } $result.AllAssetUploadsSucceeded = $true $result.Succeeded = $true return $result } } function Send-FilesToGitHubRelease([string[]] $filePathsToUpload, [string] $urlToUploadFilesTo, $authHeader) { [int] $numberOfFilesToUpload = $filePathsToUpload.Count [int] $numberOfFilesUploaded = 0 $filePathsToUpload | ForEach-Object { $filePath = $_ $fileName = Get-Item $filePath | Select-Object -ExpandProperty Name $uploadAssetWebRequestParameters = @{Uri = $urlToUploadFilesTo + "?name=$fileName" Method = 'POST' Headers = $authHeader ContentType = 'application/zip' InFile = $filePath } $numberOfFilesUploaded = $numberOfFilesUploaded + 1 Write-Verbose "Uploading asset $numberOfFilesUploaded of $numberOfFilesToUpload, '$filePath'." Invoke-RestMethodAndThrowDescriptiveErrorOnFailure $uploadAssetWebRequestParameters > $null } } function Test-AllFilePathsAndThrowErrorIfOneIsNotValid([string[]] $filePaths) { foreach ($filePath in $filePaths) { [bool] $fileWasNotFoundAtPath = [string]::IsNullOrEmpty($filePath) -or !(Test-Path -Path $filePath -PathType Leaf) if ($fileWasNotFoundAtPath) { throw "There is no file at the specified path, '$filePath'." } } } function Invoke-RestMethodAndThrowDescriptiveErrorOnFailure($requestParametersHashTable) { $requestDetailsAsNicelyFormattedString = Convert-HashTableToNicelyFormattedString $requestParametersHashTable Write-Verbose "Making web request with the following parameters:$NewLine$requestDetailsAsNicelyFormattedString" try { $webRequestResult = Invoke-RestMethod @requestParametersHashTable } catch { [Exception] $exception = $_.Exception [string] $errorMessage = Get-RestMethodExceptionDetailsOrNull -restMethodException $exception if ([string]::IsNullOrWhiteSpace($errorMessage)) { $errorMessage = $exception.ToString() } throw "An unexpected error occurred while making web request:$NewLine$errorMessage" } Write-Verbose "Web request returned the following result:$NewLine$webRequestResult" return $webRequestResult } function Get-RestMethodExceptionDetailsOrNull([Exception] $restMethodException) { try { $responseDetails = @{ResponseUri = $exception.Response.ResponseUri StatusCode = $exception.Response.StatusCode StatusDescription = $exception.Response.StatusDescription ErrorMessage = $exception.Message } [string] $responseDetailsAsNicelyFormattedString = Convert-HashTableToNicelyFormattedString $responseDetails [string] $errorInfo = "Request Details:" + $NewLine + $requestDetailsAsNicelyFormattedString $errorInfo += $NewLine $errorInfo += "Response Details:" + $NewLine + $responseDetailsAsNicelyFormattedString return $errorInfo } catch { return $null } } function Convert-HashTableToNicelyFormattedString($hashTable) { [string] $nicelyFormattedString = $hashTable.Keys | ForEach-Object { $key = $_ $value = $hashTable.$key " $key = $value$NewLine" } return $nicelyFormattedString } function Set-SecurityProtocolForThread { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls } function New-PersonalManifest { [CmdletBinding()] param([System.Collections.IDictionary] $Configuration, [string] $ManifestPath, [switch] $AddScriptsToProcess, [switch] $AddUsingsToProcess) $Manifest = $Configuration.Information.Manifest $Manifest.Path = $ManifestPath if (-not $AddScriptsToProcess) { $Manifest.ScriptsToProcess = @() } if ($AddUsingsToProcess -and $Configuration.UsingInPlace) { $Manifest.ScriptsToProcess = @($Configuration.UsingInPlace) } 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-TextWithTime -Text "[+] Converting $($Configuration.Information.Manifest.Path) UTF8 without BOM" { (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 { [alias('New-BuildModule')] [CmdletBinding()] param ([System.Collections.IDictionary] $Configuration) if (-not $Configuration) { return } $GlobalTime = [System.Diagnostics.Stopwatch]::StartNew() 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 -or $Configuration.Steps.BuildModule.EnableDesktop -or $Configuration.Steps.BuildModule.EnableCore) { Start-ModuleBuilding -Configuration $Configuration } $Execute = "$($GlobalTime.Elapsed.Days) days, $($GlobalTime.Elapsed.Hours) hours, $($GlobalTime.Elapsed.Minutes) minutes, $($GlobalTime.Elapsed.Seconds) seconds, $($GlobalTime.Elapsed.Milliseconds) milliseconds" Write-Host "[i] Module Building " -NoNewline -ForegroundColor Yellow Write-Host "[Time Total: $Execute]" -ForegroundColor Green } function New-PSMFile { [cmdletbinding()] param([string] $Path, [string[]] $FunctionNames, [string[]] $FunctionAliaes, [Array] $LibrariesCore, [Array] $LibrariesDefault, [string] $ModuleName, [switch] $UsingNamespaces) try { 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) { $Extension = $File.Substring($File.Length - 4, 4) if ($Extension -eq '.dll') { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } } '} else {' | Add-Content -Path $Path foreach ($File in $LibrariesDefault) { $Extension = $File.Substring($File.Length - 4, 4) if ($Extension -eq '.dll') { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } } '}' | Add-Content -Path $Path } elseif ($LibrariesCore.Count -gt 0) { foreach ($File in $LibrariesCore) { $Extension = $File.Substring($File.Length - 4, 4) if ($Extension -eq '.dll') { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } } } elseif ($LibrariesDefault.Count -gt 0) { foreach ($File in $LibrariesDefault) { $Extension = $File.Substring($File.Length - 4, 4) if ($Extension -eq '.dll') { $Output = 'Add-Type -Path $PSScriptRoot\' + $File $Output | Add-Content -Path $Path } } } @" Export-ModuleMember `` -Function @($Functions) `` -Alias @($Aliases) "@ | Add-Content -Path $Path } catch { $ErrorMessage = $_.Exception.Message Write-Error "New-PSM1File from $ModuleName failed build. Error: $ErrorMessage" Exit } } 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) if ($PSBoundParameters['FilePath']) { $ScriptBlockString = [IO.File]::ReadAllText((Resolve-Path $FilePath)) $ScriptBlock = [ScriptBlock]::Create($ScriptBlockString) } elseif ($PSBoundParameters['ScriptContent']) { $ScriptBlock = [ScriptBlock]::Create($ScriptContent) } else { } $OldScript = $ScriptBlock -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) { $_ } } } $SkipNext = $False $ScriptProcessing = @(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')) { $OldScript.Substring($Tokens[$i].Start, $Tokens[$i].Length) } Else { $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) { ' ' } $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')) { $OldScript.Substring($Tokens[-1].Start, $Tokens[-1].Length) } Else { $Tokens[-1].Content } }) [string] $NewScriptText = $ScriptProcessing -join '' $NewScriptText = $NewScriptText.TrimStart("`n`r;") If ($Scriptblock.Count -eq 1) { If ($Scriptblock[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] $Directory) if ($Directory) { $exists = Test-Path -Path $Directory if ($exists) { Remove-Item -Path $Directory -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 } PSAlignAssignmentStatement = @{Enable = $true CheckHashtable = $true } PSUseCorrectCasing = @{Enable = $true } } } function Set-LinkedFiles { [CmdletBinding()] param([string[]] $LinkFiles, [string] $FullModulePath, [string] $FullProjectPath, [switch] $Delete) foreach ($file in $LinkFiles) { [string] $Path = "$FullModulePath\$file" [string] $Path2 = "$FullProjectPath\$file" if ($Delete) { if (Test-ReparsePoint -path $Path) { Remove-Item $Path -Confirm:$false } } $null = cmd /c mklink $path $path2 } } function Start-ModuleBuilding { [CmdletBinding()] param([System.Collections.IDictionary] $Configuration) $DestinationPaths = @{ } if ($Configuration.Information.Manifest.CompatiblePSEditions) { if ($Configuration.Information.Manifest.CompatiblePSEditions -contains 'Desktop') { $DestinationPaths.Desktop = [IO.path]::Combine($Configuration.Information.DirectoryModules, $Configuration.Information.ModuleName) } if ($Configuration.Information.Manifest.CompatiblePSEditions -contains 'Core') { $DestinationPaths.Core = [IO.path]::Combine($Configuration.Information.DirectoryModulesCore, $Configuration.Information.ModuleName) } } [string] $Random = Get-Random 10000000000 [string] $FullModulePath = [IO.path]::GetTempPath() + '' + $Configuration.Information.ModuleName + "_TEMP_$Random" [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-Text '----------------------------------------------------' Write-Text "[i] Project Name: $ProjectName" -Color Yellow Write-Text "[i] Full module temporary path: $FullModulePath" -Color Yellow Write-Text "[i] Full project path: $FullProjectPath" -Color Yellow Write-Text "[i] Full temporary path: $FullTemporaryPath" -Color Yellow Write-Text "[i] PSScriptRoot: $PSScriptRoot" -Color Yellow Write-Text "[i] Current PSEdition: $PSEdition" -Color Yellow Write-Text "[i] Destination Desktop: $($DestinationPaths.Desktop)" -Color Yellow Write-Text "[i] Destination Core: $($DestinationPaths.Desktop)" -Color Yellow Write-Text '----------------------------------------------------' $CurrentLocation = (Get-Location).Path Set-Location -Path $FullProjectPath Remove-Directory $FullModulePath Remove-Directory $FullTemporaryPath Add-Directory $FullModulePath Add-Directory $FullTemporaryPath $LinkDirectories = @() $LinkPrivatePublicFiles = @() $Configuration.Information.Manifest.RootModule = "$($ProjectName).psm1" $Configuration.Information.Manifest.FunctionsToExport = @() $Configuration.Information.Manifest.CmdletsToExport = @() $Configuration.Information.Manifest.VariablesToExport = @() $Configuration.Information.Manifest.AliasesToExport = @() $Exclude = '.*', 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs' if ($Configuration.Steps.BuildModule) { $PreparingFilesTime = Write-Text "[+] Preparing files and folders" -Start if ($PSEdition -eq 'core') { $Directories = @($TempDirectories = Get-ChildItem -Path $FullProjectPath -Directory -Exclude $Exclude -FollowSymlink @($TempDirectories $TempDirectories | Get-ChildItem -Directory -Recurse -FollowSymlink)) $Files = Get-ChildItem -Path $FullProjectPath -Exclude $Exclude -FollowSymlink | Get-ChildItem -File -Recurse -FollowSymlink $FilesRoot = Get-ChildItem -Path "$FullProjectPath\*" -Include '*.psm1', '*.psd1', 'License*' -File -FollowSymlink } else { $Directories = @($TempDirectories = Get-ChildItem -Path $FullProjectPath -Directory -Exclude $Exclude @($TempDirectories $TempDirectories | Get-ChildItem -Directory -Recurse)) $Files = Get-ChildItem -Path $FullProjectPath -Exclude '.*', 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs' | Get-ChildItem -File -Recurse $FilesRoot = Get-ChildItem -Path "$FullProjectPath\*" -Include '*.psm1', '*.psd1', 'License*' -File } $LinkDirectories = @(foreach ($directory in $Directories) { $RelativeDirectoryPath = (Resolve-Path -LiteralPath $directory.FullName -Relative).Replace('.\', '') $RelativeDirectoryPath = "$RelativeDirectoryPath\" $RelativeDirectoryPath }) $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 | Sort-Object -Unique) { switch -Wildcard ($file) { '*.psd1' { $File } '*.psm1' { $File } 'License*' { $File } } }) $LinkPrivatePublicFiles = @(foreach ($file in $AllFiles | Sort-Object -Unique) { switch -Wildcard ($file) { '*.ps1' { Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Private', 'Public', 'Enums' continue } '*.*' { Add-FilesWithFolders -file $file -FullProjectPath $FullProjectPath -directory 'Images\', 'Resources\', 'Templates\', 'Bin\', 'Lib\' continue } } }) $LinkPrivatePublicFiles = $LinkPrivatePublicFiles | Select-Object -Unique Write-Text -End -Time $PreparingFilesTime $AliasesAndFunctions = Write-TextWithTime -Text '[+] Preparing function and aliases names' { Get-FunctionAliasesFromFolder -FullProjectPath $FullProjectPath -Files $Files } if ($AliasesAndFunctions.Function) { $Configuration.Information.Manifest.FunctionsToExport = $AliasesAndFunctions.Function } if ($AliasesAndFunctions.Alias) { $Configuration.Information.Manifest.AliasesToExport = $AliasesAndFunctions.Alias } if (-not [string]::IsNullOrWhiteSpace($Configuration.Information.ScriptsToProcess)) { $StartsWithEnums = "$($Configuration.Information.ScriptsToProcess)\" $FilesEnums = @($LinkPrivatePublicFiles | Where-Object { ($_).StartsWith($StartsWithEnums) }) if ($FilesEnums.Count -gt 0) { Write-TextWithTime -Text "[+] 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 } $LinkingFilesTime = Write-Text "[+] Linking files from root and sub directories" -Start Set-LinkedFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath Set-LinkedFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullTemporaryPath -FullProjectPath $FullProjectPath Write-Text -End -Time $LinkingFilesTime 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 -Configuration $Configuration } else { foreach ($Directory in $LinkDirectories) { $Dir = "$FullModulePath\$Directory" Add-Directory $Dir } $LinkingFilesTime = Write-Text "[+] Linking files from root and sub directories" -Start Set-LinkedFiles -LinkFiles $LinkFilesRoot -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath Set-LinkedFiles -LinkFiles $LinkPrivatePublicFiles -FullModulePath $FullModulePath -FullProjectPath $FullProjectPath Write-Text -End -Time $LinkingFilesTime } 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 ($DestinationPaths.Desktop) { Write-TextWithTime -Text "[+] Copy module to PowerShell 5 destination: $($DestinationPaths.Desktop)" { Remove-Directory -Directory $DestinationPaths.Desktop Add-Directory -Directory $DestinationPaths.Desktop Get-ChildItem -LiteralPath $FullModulePath | Copy-Item -Destination $DestinationPaths.Desktop -Recurse } } if ($DestinationPaths.Core) { Write-TextWithTime -Text "[+] Copy module to PowerShell 6/7 destination: $($DestinationPaths.Core)" { Remove-Directory -Directory $DestinationPaths.Core Add-Directory -Directory $DestinationPaths.Core Get-ChildItem -LiteralPath $FullModulePath | Copy-Item -Destination $DestinationPaths.Core -Recurse } } if ($Configuration.Steps.BuildModule.Releases) { $TagName = "v$($Configuration.Information.Manifest.ModuleVersion)" $FileName = -join ("$TagName", '.zip') $FolderPathReleases = [System.IO.Path]::Combine($FullProjectPath, 'Releases') $ZipPath = [System.IO.Path]::Combine($FullProjectPath, 'Releases', $FileName) Write-TextWithTime -Text "[+] Compressing final merged release $ZipPath" { $null = New-Item -ItemType Directory -Path $FolderPathReleases -Force if ($DestinationPaths.Desktop) { $CompressPath = [System.IO.Path]::Combine($DestinationPaths.Desktop, '*') Compress-Archive -Path $CompressPath -DestinationPath $ZipPath -Force } if ($DestinationPaths.Core -and -not $DestinationPaths.Desktop) { $CompressPath = [System.IO.Path]::Combine($DestinationPaths.Core, '*') Compress-Archive -Path $CompressPath -DestinationPath $ZipPath -Force } } if ($Configuration.Steps.PublishModule.GitHub) { if ($Configuration.Options.GitHub.FromFile) { $GitHubAccessToken = Get-Content -LiteralPath $Configuration.Options.GitHub.ApiKey } else { $GitHubAccessToken = $Configuration.Options.GitHub.ApiKey } if ($GitHubAccessToken) { if ($Configuration.Options.GitHub.RepositoryName) { $GitHubRepositoryName = $Configuration.Options.GitHub.RepositoryName } else { $GitHubRepositoryName = $ProjectName } if (Test-Path -LiteralPath $ZipPath) { } } } } if ($Configuration) { $TemporaryVerbosePreference = $VerbosePreference $VerbosePreference = $false if ($Configuration.Options.ImportModules.RequiredModules) { Write-TextWithTime -Text '[+] Importing modules - REQUIRED' { foreach ($Module in $Configuration.Information.Manifest.RequiredModules) { Import-Module -Name $Module -Force -ErrorAction Stop -Verbose:$false } } } if ($Configuration.Options.ImportModules.Self) { Write-TextWithTime -Text '[+] Importing module - SELF' { Import-Module -Name $ProjectName -Force -ErrorAction Stop -Verbose:$false } } $VerbosePreference = $TemporaryVerbosePreference if ($Configuration.Steps.BuildDocumentation) { $WarningVariablesMarkdown = @() $DocumentationPath = "$FullProjectPath\$($Configuration.Options.Documentation.Path)" $ReadMePath = "$FullProjectPath\$($Configuration.Options.Documentation.PathReadme)" Write-Text "[+] Generating documentation to $DocumentationPath with $ReadMePath" -Color Yellow 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 -WarningVariable +WarningVariablesMarkdown -WarningAction SilentlyContinue } else { $null = New-MarkdownHelp -Module $ProjectName -WithModulePage -OutputFolder $DocumentationPath -ErrorAction Stop -WarningVariable +WarningVariablesMarkdown -WarningAction SilentlyContinue $null = Move-Item -Path "$DocumentationPath\$ProjectName.md" -Destination $ReadMePath if ($Configuration.Options.Documentation.UpdateWhenNew) { $null = Update-MarkdownHelpModule $DocumentationPath -RefreshModulePage -ModulePagePath $ReadMePath -ErrorAction Stop -WarningVariable +WarningVariablesMarkdown -WarningAction SilentlyContinue } } foreach ($_ in $WarningVariablesMarkdown) { Write-Text "[-] Documentation warning: $_" -Color Yellow } } } Write-Text "[+] Cleaning up directories created in TEMP directory" -Color Yellow Remove-Directory $FullModulePath Remove-Directory $FullTemporaryPath } function Test-ReparsePoint { [CmdletBinding()] param ([string]$path) $file = Get-Item $path -Force -ea SilentlyContinue return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint) } Function Test-ScriptFile { <# .Synopsis Test a PowerShell script for cmdlets .Description This command will analyze a PowerShell script file and display a list of detected commands such as PowerShell cmdlets and functions. Commands will be compared to what is installed locally. It is recommended you run this on a Windows 8.1 client with the latest version of RSAT installed. Unknown commands could also be internally defined functions. If in doubt view the contents of the script file in the PowerShell ISE or a script editor. You can test any .ps1, .psm1 or .txt file. .Parameter Path The path to the PowerShell script file. You can test any .ps1, .psm1 or .txt file. .Example PS C:\> test-scriptfile C:\scripts\Remove-MyVM2.ps1 CommandType Name ModuleName ----------- ---- ---------- Cmdlet Disable-VMEventing Hyper-V Cmdlet ForEach-Object Microsoft.PowerShell.Core Cmdlet Get-VHD Hyper-V Cmdlet Get-VMSnapshot Hyper-V Cmdlet Invoke-Command Microsoft.PowerShell.Core Cmdlet New-PSSession Microsoft.PowerShell.Core Cmdlet Out-Null Microsoft.PowerShell.Core Cmdlet Out-String Microsoft.PowerShell.Utility Cmdlet Remove-Item Microsoft.PowerShell.Management Cmdlet Remove-PSSession Microsoft.PowerShell.Core Cmdlet Remove-VM Hyper-V Cmdlet Remove-VMSnapshot Hyper-V Cmdlet Write-Debug Microsoft.PowerShell.Utility Cmdlet Write-Verbose Microsoft.PowerShell.Utility Cmdlet Write-Warning Microsoft.PowerShell.Utility .Notes Original script provided by Jeff Hicks at (https://www.petri.com/powershell-problem-solver-find-script-commands) # https://twitter.com/donnie_taylor/status/1160920407031058432 Test-ScriptFile -Path 'C:\Users\przemyslaw.klys\Documents\WindowsPowerShell\Modules\PSWinReportingV2\PSWinReportingV2.psm1' | Sort-Object -Property Source, Name | ft -AutoSize #> [cmdletbinding()] Param([Parameter(Position = 0, Mandatory = $True, HelpMessage = "Enter the path to a PowerShell script file,", ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [ValidatePattern("\.(ps1|psm1|txt)$")] [ValidateScript( { Test-Path $_ })] [string]$Path) Begin { Write-Verbose "Starting $($MyInvocation.Mycommand)" Write-Verbose "Defining AST variables" New-Variable astTokens -force New-Variable astErr -force } Process { Write-Verbose "Parsing $path" $null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr) $h = $astTokens | Group-Object tokenflags -AsHashTable -AsString $commandData = $h.CommandName | Where-Object { $_.text -notmatch "-TargetResource$" } | ForEach-Object { Write-Verbose "Processing $($_.text)" Try { $cmd = $_.Text $resolved = $cmd | Get-Command -ErrorAction Stop if ($resolved.CommandType -eq 'Alias') { Write-Verbose "Resolving an alias" Write-Verbose "Detected the Where-Object alias '?'" if ($cmd -eq '?') { Get-Command Where-Object } else { $Resolved = $resolved.ResolvedCommandName | Get-Command [PSCustomobject]@{CommandType = $resolved.CommandType Name = $resolved.Name ModuleName = $resolved.ModuleName Source = $resolved.Source } } } else { [PSCustomobject]@{CommandType = $resolved.CommandType Name = $resolved.Name ModuleName = $resolved.ModuleName Source = $resolved.Source } } } Catch { Write-Verbose "Command is not recognized" [PSCustomobject]@{CommandType = "Unknown" Name = $cmd ModuleName = "Unknown" Source = "Unknown" } } } $CommandData } End { Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } } function Test-ScriptModule { [cmdletbinding()] param([string] $ModuleName, [ValidateSet('Name', 'CommandType', 'ModuleName', 'Source')] $SortName, [switch] $Unique) $Module = Get-Module -ListAvailable $ModuleName $Path = Join-Path -Path $Module.ModuleBase -ChildPath $Module.RootModule $Output = Test-ScriptFile -Path $Path if ($Unique) { $Output = $Output | Sort-Object -Property 'Name' -Unique:$Unique } if ($SortName) { $Output | Sort-Object -Property $SortName } else { $Output } } 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 } } } } function Write-Text { [CmdletBinding()] param([string] $Text, [System.ConsoleColor] $Color = [System.ConsoleColor]::Cyan, [System.ConsoleColor] $ColorTime = [System.ConsoleColor]::Green, [switch] $Start, [switch] $End, [System.Diagnostics.Stopwatch] $Time) if (-not $Start -and -not $End) { Write-Host "$Text" -ForegroundColor $Color } if ($Start) { Write-Host "$Text" -NoNewline -ForegroundColor $Color $Time = [System.Diagnostics.Stopwatch]::StartNew() } if ($End) { $TimeToExecute = $Time.Elapsed.ToString() Write-Host " [Time: $TimeToExecute]" -ForegroundColor $ColorTime $Time.Stop() } else { if ($Time) { return $Time } } } function Write-TextWithTime { [CmdletBinding()] param([ScriptBlock] $Content, [string] $Text, [switch] $Continue, [System.ConsoleColor] $Color = [System.ConsoleColor]::Cyan, [System.ConsoleColor] $ColorTime = [System.ConsoleColor]::Green) Write-Host "$Text" -NoNewline -ForegroundColor $Color $Time = [System.Diagnostics.Stopwatch]::StartNew() if ($null -ne $Content) { & $Content } $TimeToExecute = $Time.Elapsed.ToString() Write-Host " [Time: $TimeToExecute]" -ForegroundColor $ColorTime if (-not $Continue) { $Time.Stop() } } Export-ModuleMember -Function @('Get-GitLog', 'Get-MissingFunctions', 'Get-ScriptCommands', 'New-PrepareModule', 'Remove-Comments', 'Test-ScriptFile', 'Test-ScriptModule') -Alias @('New-BuildModule') |