├── .gitignore ├── .vscode └── launch.json ├── GitVersion.yml ├── LICENSE ├── Origin └── Publish-PSArtifactUtility.ps1 ├── ThirdPartyNotices.txt ├── build ├── build.ps1 └── tasks │ ├── BuildModule.task.ps1 │ ├── Clean.task.ps1 │ ├── CleanAll.task.ps1 │ ├── CopyModuleAssets.task.ps1 │ ├── CopyModuleBaseFiles.task.ps1 │ ├── CopyModuleSourceFiles.task.ps1 │ ├── CreatePaths.task.ps1 │ ├── ImportDependencyConfig.task.ps1 │ ├── ImportModule.task.ps1 │ ├── ImportPSModules.task.ps1 │ ├── Initialize.task.ps1 │ ├── InstallDependencies.task.ps1 │ ├── InstallDependenciesPS.task.ps1 │ ├── PublishPSModuleNuget.task.ps1 │ ├── RegisterPSRepository.task.ps1 │ ├── UpdateExportFunctionsAndAliases.task.ps1 │ └── VersionBump.task.ps1 ├── config └── dependencies.json ├── scripts └── New-ModuleManifest.ps1 └── src └── PSPublishHelper ├── PSPublishHelper.psd1 ├── PSPublishHelper.psm1 ├── private ├── Copy-PSModule.ps1 ├── Format-PSModuleDependency.ps1 ├── Get-AvailableRoleCapabilityName.ps1 ├── Get-EscapedString.ps1 ├── Get-ExportedDscResources.ps1 ├── Get-NupkgFilePath.ps1 ├── Get-NuspecContents.ps1 ├── Get-NuspecFilePath.ps1 ├── Get-PSModuleManifestData.ps1 ├── Get-TemporaryPath.ps1 ├── Resolve-NugetCommand.ps1 ├── Resolve-PSData.ps1 ├── Resolve-PSModule.ps1 ├── Resolve-PSModuleDependency.ps1 ├── Resolve-PSModuleInfo.ps1 ├── Resolve-PSModuleTags.ps1 └── Resolve-PSModuleVersion.ps1 └── public └── Publish-PSModuleNuget.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | psmodules/ 3 | tmp/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "PowerShell", 9 | "request": "launch", 10 | "name": "PowerShell Launch Current File", 11 | "script": "${file}", 12 | "args": [], 13 | "cwd": "${file}" 14 | }, 15 | { 16 | "type": "PowerShell", 17 | "request": "launch", 18 | "name": "PowerShell Launch Current File in Temporary Console", 19 | "script": "${file}", 20 | "args": [], 21 | "cwd": "${file}", 22 | "createTemporaryIntegratedConsole": true 23 | }, 24 | { 25 | "type": "PowerShell", 26 | "request": "launch", 27 | "name": "PowerShell Launch Current File w/Args Prompt", 28 | "script": "${file}", 29 | "args": [ 30 | "${command:SpecifyScriptArgs}" 31 | ], 32 | "cwd": "${file}" 33 | }, 34 | { 35 | "type": "PowerShell", 36 | "request": "attach", 37 | "name": "PowerShell Attach to Host Process", 38 | "processId": "${command:PickPSHostProcess}", 39 | "runspaceId": 1 40 | }, 41 | { 42 | "type": "PowerShell", 43 | "request": "launch", 44 | "name": "PowerShell Interactive Session", 45 | "cwd": "" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | assembly-versioning-scheme: 'MajorMinorPatchTag' 2 | assembly-informational-format: '{Major}.{Minor}.{Patch}{PreReleaseTagWithDash}+Sha.{Sha}.Date.{CommitDate}' 3 | commit-message-incrementing: MergeMessageOnly 4 | mode: ContinuousDeployment 5 | branches: 6 | master: 7 | regex: master 8 | tag: beta 9 | increment: Minor 10 | mode: ContinuousDeployment 11 | feature: 12 | regex: features?[/-] 13 | increment: Minor 14 | source-branches: ['master'] 15 | mode: ContinuousDeployment 16 | release: 17 | regex: releases?[/-] 18 | increment: Patch 19 | source-branches: ['master'] 20 | mode: ContinuousDeployment 21 | 22 | ignore: 23 | sha: [] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Mark E. Kraus 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Origin/Publish-PSArtifactUtility.ps1: -------------------------------------------------------------------------------- 1 | function Publish-PSArtifactUtility 2 | { 3 | [CmdletBinding(PositionalBinding=$false)] 4 | Param 5 | ( 6 | [Parameter(Mandatory=$true, ParameterSetName='PublishModule')] 7 | [ValidateNotNullOrEmpty()] 8 | [PSModuleInfo] 9 | $PSModuleInfo, 10 | 11 | [Parameter(Mandatory=$true, ParameterSetName='PublishScript')] 12 | [ValidateNotNullOrEmpty()] 13 | [PSCustomObject] 14 | $PSScriptInfo, 15 | 16 | [Parameter(Mandatory=$true, ParameterSetName='PublishModule')] 17 | [ValidateNotNullOrEmpty()] 18 | [string] 19 | $ManifestPath, 20 | 21 | [Parameter(Mandatory=$true)] 22 | [ValidateNotNullOrEmpty()] 23 | [string] 24 | $Destination, 25 | 26 | [Parameter(Mandatory=$true)] 27 | [ValidateNotNullOrEmpty()] 28 | [string] 29 | $Repository, 30 | 31 | [Parameter(Mandatory=$true)] 32 | [ValidateNotNullOrEmpty()] 33 | [string] 34 | $NugetApiKey, 35 | 36 | [Parameter(Mandatory=$false)] 37 | [pscredential] 38 | $Credential, 39 | 40 | [Parameter(Mandatory=$true)] 41 | [ValidateNotNullOrEmpty()] 42 | [string] 43 | $NugetPackageRoot, 44 | 45 | [Parameter(ParameterSetName='PublishModule')] 46 | [Version] 47 | $FormatVersion, 48 | 49 | [Parameter(ParameterSetName='PublishModule')] 50 | [string] 51 | $ReleaseNotes, 52 | 53 | [Parameter(ParameterSetName='PublishModule')] 54 | [string[]] 55 | $Tags, 56 | 57 | [Parameter(ParameterSetName='PublishModule')] 58 | [Uri] 59 | $LicenseUri, 60 | 61 | [Parameter(ParameterSetName='PublishModule')] 62 | [Uri] 63 | $IconUri, 64 | 65 | [Parameter(ParameterSetName='PublishModule')] 66 | [Uri] 67 | $ProjectUri 68 | ) 69 | 70 | Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe 71 | 72 | $PSArtifactType = $script:PSArtifactTypeModule 73 | $Name = $null 74 | $Description = $null 75 | $Version = "" 76 | $Author = $null 77 | $CompanyName = $null 78 | $Copyright = $null 79 | $requireLicenseAcceptance = "false" 80 | 81 | if($PSModuleInfo) 82 | { 83 | $Name = $PSModuleInfo.Name 84 | $Description = $PSModuleInfo.Description 85 | $Version = $PSModuleInfo.Version 86 | $Author = $PSModuleInfo.Author 87 | $CompanyName = $PSModuleInfo.CompanyName 88 | $Copyright = $PSModuleInfo.Copyright 89 | 90 | if($PSModuleInfo.PrivateData -and 91 | ($PSModuleInfo.PrivateData.GetType().ToString() -eq "System.Collections.Hashtable") -and 92 | $PSModuleInfo.PrivateData["PSData"] -and 93 | ($PSModuleInfo.PrivateData["PSData"].GetType().ToString() -eq "System.Collections.Hashtable") 94 | ) 95 | { 96 | if( -not $Tags -and $PSModuleInfo.PrivateData.PSData["Tags"]) 97 | { 98 | $Tags = $PSModuleInfo.PrivateData.PSData.Tags 99 | } 100 | 101 | if( -not $ReleaseNotes -and $PSModuleInfo.PrivateData.PSData["ReleaseNotes"]) 102 | { 103 | $ReleaseNotes = $PSModuleInfo.PrivateData.PSData.ReleaseNotes 104 | } 105 | 106 | if( -not $LicenseUri -and $PSModuleInfo.PrivateData.PSData["LicenseUri"]) 107 | { 108 | $LicenseUri = $PSModuleInfo.PrivateData.PSData.LicenseUri 109 | } 110 | 111 | if( -not $IconUri -and $PSModuleInfo.PrivateData.PSData["IconUri"]) 112 | { 113 | $IconUri = $PSModuleInfo.PrivateData.PSData.IconUri 114 | } 115 | 116 | if( -not $ProjectUri -and $PSModuleInfo.PrivateData.PSData["ProjectUri"]) 117 | { 118 | $ProjectUri = $PSModuleInfo.PrivateData.PSData.ProjectUri 119 | } 120 | 121 | if ($PSModuleInfo.PrivateData.PSData["Prerelease"]) 122 | { 123 | $psmoduleInfoPrereleaseString = $PSModuleInfo.PrivateData.PSData.Prerelease 124 | if ($psmoduleInfoPrereleaseString -and $psmoduleInfoPrereleaseString.StartsWith("-")) 125 | { 126 | $Version = [string]$Version + $psmoduleInfoPrereleaseString 127 | } 128 | else 129 | { 130 | $Version = [string]$Version + "-" + $psmoduleInfoPrereleaseString 131 | } 132 | } 133 | 134 | if($PSModuleInfo.PrivateData.PSData["RequireLicenseAcceptance"]) 135 | { 136 | $requireLicenseAcceptance = $PSModuleInfo.PrivateData.PSData.requireLicenseAcceptance.ToString().ToLower() 137 | if($requireLicenseAcceptance -eq "true") 138 | { 139 | if($FormatVersion -and ($FormatVersion.Major -lt $script:PSGetRequireLicenseAcceptanceFormatVersion.Major)) 140 | { 141 | $message = $LocalizedData.requireLicenseAcceptanceNotSupported -f($FormatVersion) 142 | ThrowError -ExceptionName "System.InvalidOperationException" ` 143 | -ExceptionMessage $message ` 144 | -ErrorId "requireLicenseAcceptanceNotSupported" ` 145 | -CallerPSCmdlet $PSCmdlet ` 146 | -ErrorCategory InvalidData 147 | } 148 | 149 | if(-not $LicenseUri) 150 | { 151 | $message = $LocalizedData.LicenseUriNotSpecified 152 | ThrowError -ExceptionName "System.InvalidOperationException" ` 153 | -ExceptionMessage $message ` 154 | -ErrorId "LicenseUriNotSpecified" ` 155 | -CallerPSCmdlet $PSCmdlet ` 156 | -ErrorCategory InvalidData 157 | } 158 | 159 | $LicenseFilePath = Join-PathUtility -Path $NugetPackageRoot -ChildPath 'License.txt' -PathType File 160 | if(-not $LicenseFilePath -or -not (Test-Path -Path $LicenseFilePath -PathType Leaf)) 161 | { 162 | $message = $LocalizedData.LicenseTxtNotFound 163 | ThrowError -ExceptionName "System.InvalidOperationException" ` 164 | -ExceptionMessage $message ` 165 | -ErrorId "LicenseTxtNotFound" ` 166 | -CallerPSCmdlet $PSCmdlet ` 167 | -ErrorCategory InvalidData 168 | } 169 | 170 | if((Get-Content -LiteralPath $LicenseFilePath) -eq $null) 171 | { 172 | $message = $LocalizedData.LicenseTxtEmpty 173 | ThrowError -ExceptionName "System.InvalidOperationException" ` 174 | -ExceptionMessage $message ` 175 | -ErrorId "LicenseTxtEmpty" ` 176 | -CallerPSCmdlet $PSCmdlet ` 177 | -ErrorCategory InvalidData 178 | } 179 | 180 | #RequireLicenseAcceptance is true, License uri and license.txt exist. Bump Up the FormatVersion 181 | if(-not $FormatVersion) 182 | { 183 | $FormatVersion = $script:CurrentPSGetFormatVersion 184 | } 185 | } 186 | elseif($requireLicenseAcceptance -ne "false") 187 | { 188 | $InvalidValueForRequireLicenseAcceptance = $LocalizedData.InvalidValueBoolean -f ($requireLicenseAcceptance, "requireLicenseAcceptance") 189 | Write-Warning -Message $InvalidValueForRequireLicenseAcceptance 190 | } 191 | } 192 | } 193 | } 194 | else 195 | { 196 | $PSArtifactType = $script:PSArtifactTypeScript 197 | 198 | $Name = $PSScriptInfo.Name 199 | $Description = $PSScriptInfo.Description 200 | $Version = $PSScriptInfo.Version 201 | $Author = $PSScriptInfo.Author 202 | $CompanyName = $PSScriptInfo.CompanyName 203 | $Copyright = $PSScriptInfo.Copyright 204 | 205 | if($PSScriptInfo.'Tags') 206 | { 207 | $Tags = $PSScriptInfo.Tags 208 | } 209 | 210 | if($PSScriptInfo.'ReleaseNotes') 211 | { 212 | $ReleaseNotes = $PSScriptInfo.ReleaseNotes 213 | } 214 | 215 | if($PSScriptInfo.'LicenseUri') 216 | { 217 | $LicenseUri = $PSScriptInfo.LicenseUri 218 | } 219 | 220 | if($PSScriptInfo.'IconUri') 221 | { 222 | $IconUri = $PSScriptInfo.IconUri 223 | } 224 | 225 | if($PSScriptInfo.'ProjectUri') 226 | { 227 | $ProjectUri = $PSScriptInfo.ProjectUri 228 | } 229 | } 230 | 231 | 232 | # Add PSModule and PSGet format version tags 233 | if(-not $Tags) 234 | { 235 | $Tags = @() 236 | } 237 | 238 | if($FormatVersion) 239 | { 240 | $Tags += "$($script:PSGetFormatVersion)_$FormatVersion" 241 | } 242 | 243 | $DependentModuleDetails = @() 244 | 245 | if($PSScriptInfo) 246 | { 247 | $Tags += "PSScript" 248 | 249 | if($PSScriptInfo.DefinedCommands) 250 | { 251 | if($PSScriptInfo.DefinedFunctions) 252 | { 253 | $Tags += "$($script:Includes)_Function" 254 | $Tags += $PSScriptInfo.DefinedFunctions | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } 255 | } 256 | 257 | if($PSScriptInfo.DefinedWorkflows) 258 | { 259 | $Tags += "$($script:Includes)_Workflow" 260 | $Tags += $PSScriptInfo.DefinedWorkflows | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Workflow)_$_" } 261 | } 262 | 263 | $Tags += $PSScriptInfo.DefinedCommands | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Command)_$_" } 264 | } 265 | 266 | # Populate the dependencies elements from RequiredModules and RequiredScripts 267 | # 268 | $ValidateAndGetScriptDependencies_Params = @{ 269 | Repository=$Repository 270 | DependentScriptInfo=$PSScriptInfo 271 | CallerPSCmdlet=$PSCmdlet 272 | Verbose=$VerbosePreference 273 | Debug=$DebugPreference 274 | } 275 | if ($PSBoundParameters.ContainsKey('Credential')) 276 | { 277 | $ValidateAndGetScriptDependencies_Params.Add('Credential',$Credential) 278 | } 279 | $DependentModuleDetails += ValidateAndGet-ScriptDependencies @ValidateAndGetScriptDependencies_Params 280 | } 281 | else 282 | { 283 | $Tags += "PSModule" 284 | 285 | $ModuleManifestHashTable = Get-ManifestHashTable -Path $ManifestPath 286 | 287 | if($PSModuleInfo.ExportedCommands.Count) 288 | { 289 | if($PSModuleInfo.ExportedCmdlets.Count) 290 | { 291 | $Tags += "$($script:Includes)_Cmdlet" 292 | $Tags += $PSModuleInfo.ExportedCmdlets.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Cmdlet)_$_" } 293 | 294 | #if CmdletsToExport field in manifest file is "*", we suggest the user to include all those cmdlets for best practice 295 | if($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('CmdletsToExport') -and ($ModuleManifestHashTable.CmdletsToExport -eq "*")) 296 | { 297 | $WarningMessage = $LocalizedData.ShouldIncludeCmdletsToExport -f ($ManifestPath) 298 | Write-Warning -Message $WarningMessage 299 | } 300 | } 301 | 302 | if($PSModuleInfo.ExportedFunctions.Count) 303 | { 304 | $Tags += "$($script:Includes)_Function" 305 | $Tags += $PSModuleInfo.ExportedFunctions.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Function)_$_" } 306 | 307 | if($ModuleManifestHashTable -and $ModuleManifestHashTable.ContainsKey('FunctionsToExport') -and ($ModuleManifestHashTable.FunctionsToExport -eq "*")) 308 | { 309 | $WarningMessage = $LocalizedData.ShouldIncludeFunctionsToExport -f ($ManifestPath) 310 | Write-Warning -Message $WarningMessage 311 | } 312 | } 313 | 314 | $Tags += $PSModuleInfo.ExportedCommands.Keys | Microsoft.PowerShell.Core\ForEach-Object { "$($script:Command)_$_" } 315 | } 316 | 317 | if(!$IsCoreCLR) { 318 | $dscResourceNames = Get-ExportedDscResources -PSModuleInfo $PSModuleInfo 319 | } else { 320 | Write-Verbose 'Skipping DSC resource enumeration as it is not supported on PS Core.' 321 | Write-Verbose 'Please use Windows PowerShell to build DSC modules.' 322 | } 323 | if($dscResourceNames) 324 | { 325 | $Tags += "$($script:Includes)_DscResource" 326 | 327 | $Tags += $dscResourceNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:DscResource)_$_" } 328 | 329 | #If DscResourcesToExport is commented out or "*" is used, we will write-warning 330 | if($ModuleManifestHashTable -and 331 | ($ModuleManifestHashTable.ContainsKey("DscResourcesToExport") -and 332 | $ModuleManifestHashTable.DscResourcesToExport -eq "*") -or 333 | -not $ModuleManifestHashTable.ContainsKey("DscResourcesToExport")) 334 | { 335 | $WarningMessage = $LocalizedData.ShouldIncludeDscResourcesToExport -f ($ManifestPath) 336 | Write-Warning -Message $WarningMessage 337 | } 338 | } 339 | 340 | $RoleCapabilityNames = Get-AvailableRoleCapabilityName -PSModuleInfo $PSModuleInfo 341 | if($RoleCapabilityNames) 342 | { 343 | $Tags += "$($script:Includes)_RoleCapability" 344 | 345 | $Tags += $RoleCapabilityNames | Microsoft.PowerShell.Core\ForEach-Object { "$($script:RoleCapability)_$_" } 346 | } 347 | 348 | # Populate the module dependencies elements from RequiredModules and 349 | # NestedModules properties of the current PSModuleInfo 350 | $GetModuleDependencies_Params = @{ 351 | PSModuleInfo=$PSModuleInfo 352 | Repository=$Repository 353 | CallerPSCmdlet=$PSCmdlet 354 | Verbose=$VerbosePreference 355 | Debug=$DebugPreference 356 | } 357 | if ($PSBoundParameters.ContainsKey('Credential')) 358 | { 359 | $GetModuleDependencies_Params.Add('Credential',$Credential) 360 | } 361 | $DependentModuleDetails = Get-ModuleDependencies @GetModuleDependencies_Params 362 | } 363 | 364 | $dependencies = @() 365 | ForEach($Dependency in $DependentModuleDetails) 366 | { 367 | $ModuleName = $Dependency.Name 368 | $VersionString = $null 369 | 370 | # Version format in NuSpec: 371 | # "[2.0]" --> (== 2.0) Required Version 372 | # "2.0" --> (>= 2.0) Minimum Version 373 | # 374 | # When only MaximumVersion is specified in the ModuleSpecification 375 | # (,1.0] = x <= 1.0 376 | # 377 | # When both Minimum and Maximum versions are specified in the ModuleSpecification 378 | # [1.0,2.0] = 1.0 <= x <= 2.0 379 | 380 | if($Dependency.Keys -Contains "RequiredVersion") 381 | { 382 | $VersionString = "[$($Dependency.RequiredVersion)]" 383 | } 384 | elseif($Dependency.Keys -Contains 'MinimumVersion' -and $Dependency.Keys -Contains 'MaximumVersion') 385 | { 386 | $VersionString = "[$($Dependency.MinimumVersion),$($Dependency.MaximumVersion)]" 387 | } 388 | elseif($Dependency.Keys -Contains 'MaximumVersion') 389 | { 390 | $VersionString = "(,$($Dependency.MaximumVersion)]" 391 | } 392 | elseif($Dependency.Keys -Contains 'MinimumVersion') 393 | { 394 | $VersionString = "$($Dependency.MinimumVersion)" 395 | } 396 | 397 | if ([System.string]::IsNullOrWhiteSpace($VersionString)) 398 | { 399 | $dependencies += "" 400 | } 401 | else 402 | { 403 | $dependencies += "" 404 | } 405 | } 406 | 407 | # Populate the nuspec elements 408 | $nuspec = @" 409 | 410 | 411 | 412 | $(Get-EscapedString -ElementValue "$Name") 413 | $($Version) 414 | $(Get-EscapedString -ElementValue "$Author") 415 | $(Get-EscapedString -ElementValue "$CompanyName") 416 | $(Get-EscapedString -ElementValue "$Description") 417 | $(Get-EscapedString -ElementValue "$ReleaseNotes") 418 | $($requireLicenseAcceptance.ToString()) 419 | $(Get-EscapedString -ElementValue "$Copyright") 420 | $(if($Tags){ Get-EscapedString -ElementValue ($Tags -join ' ')}) 421 | $(if($LicenseUri){ 422 | "$(Get-EscapedString -ElementValue "$LicenseUri")" 423 | }) 424 | $(if($ProjectUri){ 425 | "$(Get-EscapedString -ElementValue "$ProjectUri")" 426 | }) 427 | $(if($IconUri){ 428 | "$(Get-EscapedString -ElementValue "$IconUri")" 429 | }) 430 | 431 | $dependencies 432 | 433 | 434 | 435 | "@ 436 | 437 | # When packaging we must build something. 438 | # So, we are building an empty assembly called NotUsed, and discarding it. 439 | $CsprojContent = @" 440 | 441 | 442 | NotUsed 443 | Temp project used for creating nupkg file. 444 | $Name.nuspec 445 | $NugetPackageRoot 446 | netcoreapp2.0 447 | 448 | 449 | "@ 450 | $NupkgPath = Microsoft.PowerShell.Management\Join-Path -Path $NugetPackageRoot -ChildPath "$Name.$Version.nupkg" 451 | 452 | $csprojBasePath = $null 453 | if($script:DotnetCommandPath) { 454 | $csprojBasePath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath -ChildPath ([System.Guid]::NewGuid()) 455 | $null = Microsoft.PowerShell.Management\New-Item -Path $csprojBasePath -ItemType Directory -Force -WhatIf:$false -Confirm:$false 456 | $NuspecPath = Microsoft.PowerShell.Management\Join-Path -Path $csprojBasePath -ChildPath "$Name.nuspec" 457 | $CsprojFilePath = Microsoft.PowerShell.Management\Join-Path -Path $csprojBasePath -ChildPath "$Name.csproj" 458 | } 459 | else { 460 | $NuspecPath = Microsoft.PowerShell.Management\Join-Path -Path $NugetPackageRoot -ChildPath "$Name.nuspec" 461 | } 462 | 463 | $tempErrorFile = $null 464 | $tempOutputFile = $null 465 | 466 | try 467 | { 468 | # Remove existing nuspec and nupkg files 469 | if($NupkgPath -and (Test-Path -Path $NupkgPath -PathType Leaf)) 470 | { 471 | Microsoft.PowerShell.Management\Remove-Item $NupkgPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 472 | } 473 | 474 | if($NuspecPath -and (Test-Path -Path $NuspecPath -PathType Leaf)) 475 | { 476 | Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 477 | } 478 | 479 | Microsoft.PowerShell.Management\Set-Content -Value $nuspec -Path $NuspecPath -Force -Confirm:$false -WhatIf:$false 480 | 481 | # Create .nupkg file 482 | if($script:DotnetCommandPath) { 483 | Microsoft.PowerShell.Management\Set-Content -Value $CsprojContent -Path $CsprojFilePath -Force -Confirm:$false -WhatIf:$false 484 | 485 | $arguments = @('pack') 486 | $arguments += $csprojBasePath 487 | $arguments += @('--output',$NugetPackageRoot) 488 | $arguments += "/p:StagingPath=$NugetPackageRoot" 489 | $output = & $script:DotnetCommandPath $arguments 490 | Write-Debug -Message "dotnet pack output: $output" 491 | } 492 | elseif($script:NuGetExePath) { 493 | $output = & $script:NuGetExePath pack $NuspecPath -OutputDirectory $NugetPackageRoot 494 | } 495 | 496 | if(-not (Test-Path -Path $NupkgPath -PathType Leaf)) { 497 | $SemanticVersionString = Get-NormalizedVersionString -Version $Version 498 | $NupkgPath = Join-PathUtility -Path $NugetPackageRoot -ChildPath "$Name.$($SemanticVersionString).nupkg" -PathType File 499 | } 500 | 501 | if($LASTEXITCODE -or -not $NupkgPath -or -not (Test-Path -Path $NupkgPath -PathType Leaf)) 502 | { 503 | if($PSArtifactType -eq $script:PSArtifactTypeModule) 504 | { 505 | $message = $LocalizedData.FailedToCreateCompressedModule -f ($output) 506 | $errorId = "FailedToCreateCompressedModule" 507 | } 508 | else 509 | { 510 | $message = $LocalizedData.FailedToCreateCompressedScript -f ($output) 511 | $errorId = "FailedToCreateCompressedScript" 512 | } 513 | 514 | Write-Error -Message $message -ErrorId $errorId -Category InvalidOperation 515 | return 516 | } 517 | 518 | # Publish the .nupkg to gallery 519 | $tempErrorFile = Microsoft.PowerShell.Management\Join-Path -Path $nugetPackageRoot -ChildPath "TempPublishError.txt" 520 | $tempOutputFile = Microsoft.PowerShell.Management\Join-Path -Path $nugetPackageRoot -ChildPath "TempPublishOutput.txt" 521 | 522 | $errorMsg = $null 523 | $outputMsg = $null 524 | $StartProcess_params = @{ 525 | RedirectStandardError = $tempErrorFile 526 | RedirectStandardOutput = $tempOutputFile 527 | NoNewWindow = $true 528 | Wait = $true 529 | PassThru = $true 530 | } 531 | 532 | if($script:DotnetCommandPath) { 533 | $StartProcess_params['FilePath'] = $script:DotnetCommandPath 534 | 535 | $ArgumentList = @('nuget') 536 | $ArgumentList += 'push' 537 | $ArgumentList += "`"$NupkgPath`"" 538 | $ArgumentList += @('--source', "`"$($Destination.TrimEnd('\'))`"") 539 | $ArgumentList += @('--api-key', "`"$NugetApiKey`"") 540 | } 541 | elseif($script:NuGetExePath) { 542 | $StartProcess_params['FilePath'] = $script:NuGetExePath 543 | 544 | $ArgumentList = @('push') 545 | $ArgumentList += "`"$NupkgPath`"" 546 | $ArgumentList += @('-source', "`"$($Destination.TrimEnd('\'))`"") 547 | $ArgumentList += @('-apikey', "`"$NugetApiKey`"") 548 | $ArgumentList += '-NonInteractive' 549 | } 550 | $StartProcess_params['ArgumentList'] = $ArgumentList 551 | 552 | if($script:IsCoreCLR -and -not $script:IsNanoServer) { 553 | $StartProcess_params['WhatIf'] = $false 554 | $StartProcess_params['Confirm'] = $false 555 | } 556 | 557 | $process = Microsoft.PowerShell.Management\Start-Process @StartProcess_params 558 | 559 | if(Test-Path -Path $tempErrorFile -PathType Leaf) { 560 | $errorMsg = Microsoft.PowerShell.Management\Get-Content -Path $tempErrorFile -Raw 561 | 562 | if($errorMsg) { 563 | Write-Verbose -Message $errorMsg 564 | } 565 | } 566 | 567 | if(Test-Path -Path $tempOutputFile -PathType Leaf) { 568 | $outputMsg = Microsoft.PowerShell.Management\Get-Content -Path $tempOutputFile -Raw 569 | 570 | if($outputMsg) { 571 | Write-Verbose -Message $outputMsg 572 | } 573 | } 574 | 575 | # The newer version of dotnet cli writes the error message into output stream instead of error stream 576 | # Get the error message from output stream when ExitCode is non zero (error). 577 | if($process -and $process.ExitCode -and -not $errorMsg -and $outputMsg) { 578 | $errorMsg = $outputMsg 579 | } 580 | 581 | if(-not $process -or $process.ExitCode) 582 | { 583 | if(($NugetApiKey -eq 'VSTS') -and 584 | ($errorMsg -match 'Cannot prompt for input in non-interactive mode.') ) 585 | { 586 | $errorMsg = $LocalizedData.RegisterVSTSFeedAsNuGetPackageSource -f ($Destination, $script:VSTSAuthenticatedFeedsDocUrl) 587 | } 588 | 589 | if($PSArtifactType -eq $script:PSArtifactTypeModule) 590 | { 591 | $message = $LocalizedData.FailedToPublish -f ($Name,$errorMsg) 592 | $errorId = "FailedToPublishTheModule" 593 | } 594 | else 595 | { 596 | $message = $LocalizedData.FailedToPublishScript -f ($Name,$errorMsg) 597 | $errorId = "FailedToPublishTheScript" 598 | } 599 | 600 | Write-Error -Message $message -ErrorId $errorId -Category InvalidOperation 601 | } 602 | else 603 | { 604 | if($PSArtifactType -eq $script:PSArtifactTypeModule) 605 | { 606 | $message = $LocalizedData.PublishedSuccessfully -f ($Name, $Destination, $Name) 607 | } 608 | else 609 | { 610 | $message = $LocalizedData.PublishedScriptSuccessfully -f ($Name, $Destination, $Name) 611 | } 612 | 613 | Write-Verbose -Message $message 614 | } 615 | } 616 | finally 617 | { 618 | if($NupkgPath -and (Test-Path -Path $NupkgPath -PathType Leaf)) 619 | { 620 | Microsoft.PowerShell.Management\Remove-Item $NupkgPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 621 | } 622 | 623 | if($NuspecPath -and (Test-Path -Path $NuspecPath -PathType Leaf)) 624 | { 625 | Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 626 | } 627 | 628 | if($tempErrorFile -and (Test-Path -Path $tempErrorFile -PathType Leaf)) 629 | { 630 | Microsoft.PowerShell.Management\Remove-Item $tempErrorFile -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 631 | } 632 | 633 | if($tempOutputFile -and (Test-Path -Path $tempOutputFile -PathType Leaf)) 634 | { 635 | Microsoft.PowerShell.Management\Remove-Item $tempOutputFile -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 636 | } 637 | 638 | if($csprojBasePath -and (Test-Path -Path $csprojBasePath -PathType Container)) 639 | { 640 | Microsoft.PowerShell.Management\Remove-Item -Path $csprojBasePath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false 641 | } 642 | } 643 | } 644 | -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | THIRD PARTY SOFTWARE NOTICES AND INFORMATION 2 | 3 | This project uses software provided by third parties, including open source software. The following copyright statements and licenses apply to various components that are distributed with various parts of the project. The project that includes this file does not necessarily use all of the third party software components referred to below. 4 | 5 | Licensee must fully agree and comply with these license terms or must not use these components. The third party license terms apply only to the respective software to which the license pertains, and the third party license terms do not apply to the Hangfire software. 6 | 7 | In the event that we accidentally failed to list a required notice, please bring it to our attention by filing an issue in our code repository. 8 | 9 | ------------------------------------------------------------------- 10 | 11 | PowerShellGet 12 | https://github.com/PowerShell/PowerShellGet 13 | 14 | PowerShellGet 15 | 16 | Copyright (c) Microsoft Corporation 17 | 18 | All rights reserved. 19 | 20 | The MIT License (MIT) 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining a copy 23 | of this software and associated documentation files (the "Software"), to deal 24 | in the Software without restriction, including without limitation the rights 25 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 26 | copies of the Software, and to permit persons to whom the Software is 27 | furnished to do so, subject to the following conditions: 28 | 29 | The above copyright notice and this permission notice shall be included in all 30 | copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 38 | SOFTWARE. 39 | 40 | ------------------------------------------------------------------- 41 | -------------------------------------------------------------------------------- /build/build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter()] 4 | [string] 5 | $ProjectRoot = (Resolve-Path (Join-path $BuildRoot '..')).Path, 6 | 7 | [Parameter()] 8 | [string] 9 | $OutputPath, 10 | 11 | [Parameter()] 12 | [string] 13 | $Phase = 'build', 14 | 15 | [Parameter()] 16 | [string] 17 | $PSGalleryApiKey, 18 | 19 | [Parameter()] 20 | [string] 21 | $PSRepositoryName = $env:PSRepositoryName, 22 | 23 | [Parameter()] 24 | [string] 25 | $PSRepositoryUrl = $env:PSRepositoryUrl, 26 | 27 | [Parameter()] 28 | [ValidateSet('','none','basic','apikey')] 29 | [string] 30 | $PSRepositoryAuthMethod = $env:PSRepositoryAuthMethod, 31 | 32 | [Parameter()] 33 | [string] 34 | $PSRepositoryUser = $env:PSRepositoryUser, 35 | 36 | [Parameter()] 37 | [string] 38 | $PSRepositoryPassword = $env:PSRepositoryPassword, 39 | 40 | [Parameter()] 41 | [string] 42 | $PSRepositoryApiKey = $env:PSRepositoryApiKey, 43 | 44 | [Parameter()] 45 | [string] 46 | $BuildVersion = $env:GitVersion_NuGetVersionV2 47 | ) 48 | 49 | Set-BuildHeader { 50 | param($Path) 51 | Write-Build Green ('=' * 80) 52 | Write-Build Green (' Task {0}' -f $Path) 53 | Write-Build Green ('At {0}:{1}' -f $Task.InvocationInfo.ScriptName, $Task.InvocationInfo.ScriptLineNumber) 54 | if(($Synopsis = Get-BuildSynopsis $Task)) { 55 | Write-Build Green (' {0}' -f $Synopsis) 56 | } 57 | Write-Build Green ('-' * 80) 58 | # task location in a script 59 | Write-Build Green ' ' 60 | } 61 | 62 | # Define footers similar to default but change the color to DarkGray. 63 | Set-BuildFooter { 64 | param($Path) 65 | Write-Build Green ' ' 66 | Write-Build Green ('=' * 80) 67 | Write-Build DarkGray ('Done {0}, {1}' -f $Path, $Task.Elapsed) 68 | Write-Build Green ' ' 69 | Write-Build Green ' ' 70 | } 71 | 72 | 73 | $TasksFolder = Join-Path $PSScriptRoot 'tasks' 74 | Get-ChildItem -Path $TasksFolder -Filter "*.task.ps1" | ForEach-Object { 75 | . $_.FullName 76 | } 77 | 78 | Task . Build -------------------------------------------------------------------------------- /build/tasks/BuildModule.task.ps1: -------------------------------------------------------------------------------- 1 | Task BuildModule { 2 | $Script:Phase = 'Build' 3 | }, Initialize, CreatePaths, InstallDependencies, CopyModuleBaseFiles, CopyModuleSourceFiles, CopyModuleAssets, UpdateExportFunctionsAndAliases, VersionBump -------------------------------------------------------------------------------- /build/tasks/Clean.task.ps1: -------------------------------------------------------------------------------- 1 | Task Clean Initialize, { 2 | 'Removing OutputPath contents' 3 | Get-ChildItem -Path $OutputPath -Force -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -Verbose 4 | } -------------------------------------------------------------------------------- /build/tasks/CleanAll.task.ps1: -------------------------------------------------------------------------------- 1 | Task CleanAll Initialize, { 2 | 'Cleaning all project directories' 3 | foreach($Path in $Script:ProjectDirectories) { 4 | try { 5 | 'Removing {0}...' -f $Path 6 | Remove-Item -Path $Path -Recurse -Force -ErrorAction Stop 7 | 'Removed path {0}.' -f $Path 8 | } catch [System.Management.Automation.ItemNotFoundException] { 9 | '{0} was already removed.' -f $Path 10 | } catch { 11 | Write-Error -ErrorRecord $_ 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /build/tasks/CopyModuleAssets.task.ps1: -------------------------------------------------------------------------------- 1 | Task CopyModuleAssets Initialize, { 2 | if(-not (Test-Path -PathType Container -Path $ModuleSrcAssetsPath)) { 3 | 'Assets directory {0} does not exist. skipping.' -f $ModuleSrcAssetsPath 4 | return 5 | } 6 | 'Copying assets from {0} to ' -f $ModuleSrcAssetsPath, $ModuleOutputAssetsPath 7 | $Null = New-Item -Path $ModuleOutputAssetsPath -Force -ItemType Directory 8 | Get-ChildItem $ModuleSrcAssetsPath -Force | ForEach-Object { 9 | if($_.PSIsContainer) { 10 | 'Copying Directory {0}' -f $_.FullName 11 | Copy-Item -Container -Recurse -Path $_.FullName -Destination $ModuleOutputAssetsPath -Force 12 | } else { 13 | 'Copying file {0}' -f $_.FullName 14 | Copy-Item -Path $_.FullName -Destination $ModuleOutputAssetsPath -Force 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /build/tasks/CopyModuleBaseFiles.task.ps1: -------------------------------------------------------------------------------- 1 | Task CopyModuleBaseFiles Initialize, CreatePaths, { 2 | 'Copying base module files from {0} to {1}' -f $ModuleSrcPath, $ModuleOutputPath 3 | $Null = Push-Location $ModuleSrcPath 4 | Copy-Item *.psm1, *.psd1, *.ps1xml -Destination $ModuleOutputPath -Force -Verbose 5 | $Null = Pop-Location 6 | } -------------------------------------------------------------------------------- /build/tasks/CopyModuleSourceFiles.task.ps1: -------------------------------------------------------------------------------- 1 | Task CopyModuleSourceFiles Initialize, CreatePaths, { 2 | 'Copying module source files into {0}' -f $ModuleOutputRootModuleFile 3 | $Null = Push-Location $ProjectRoot 4 | 5 | $AddContentCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Add-Content', [System.Management.Automation.CommandTypes]::Cmdlet) 6 | $AddContent = {& $AddContentCmd -Path $ModuleOutputRootModuleFile -Encoding Utf8 }.GetSteppablePipeline($myInvocation.CommandOrigin) 7 | $AddContent.Begin($true) 8 | 9 | $Folders = @( 10 | $ModuleSrcEnumsPath 11 | $ModuleSrcClassesPath 12 | $ModuleSrcPrivatePath 13 | $ModuleSrcPublicPath 14 | ) 15 | foreach ($Folder in $Folders) { 16 | if(-not (Test-Path -PathType Container -Path $Folder)){ 17 | '{0} does not existing. skipping' -f $Folder 18 | continue 19 | } 20 | 'Copying source from {0} into {1}' -f $Folder, $ModuleOutputRootModuleFile 21 | $Params = @{ 22 | Filter = '*.ps1' 23 | Path = $Folder 24 | Recurse = $true 25 | File = $true 26 | Force = $true 27 | } 28 | Get-ChildItem @Params | ForEach-Object { 29 | 'Processing {0}' -f $_.FullName 30 | $FileName = Resolve-Path -Path $_.FullName -Relative 31 | $Prefix = "#Region '{0}' 0" -f $FileName 32 | $AddContent.Process($Prefix) 33 | Get-Content $_.FullName -OutVariable Content | ForEach-Object { 34 | $AddContent.Process($_) 35 | } 36 | $Suffix = "#EndRegion '{0}' {1}" -f $FileName, $Content.Count 37 | $AddContent.Process($Suffix) 38 | } 39 | } 40 | $AddContent.End() 41 | $Null = Pop-Location 42 | } -------------------------------------------------------------------------------- /build/tasks/CreatePaths.task.ps1: -------------------------------------------------------------------------------- 1 | Task CreatePaths Initialize, { 2 | 'Creating project directories' 3 | foreach($Path in $Script:ProjectDirectories) { 4 | New-Item -ItemType Directory -Path $Path -Force | ForEach-Object -MemberName FullName 5 | } 6 | } -------------------------------------------------------------------------------- /build/tasks/ImportDependencyConfig.task.ps1: -------------------------------------------------------------------------------- 1 | Task ImportDependencyConfig Initialize, { 2 | 'Importing dependency configuration from {0}' -f $DependenciesFile 3 | $Script:DependencyConfig = Get-Content -Raw $DependenciesFile -ErrorAction stop | ConvertFrom-Json -ErrorAction stop 4 | } -------------------------------------------------------------------------------- /build/tasks/ImportModule.task.ps1: -------------------------------------------------------------------------------- 1 | Task ImportModule Initialize, { 2 | 'Importing module {0}' -f $ModuleOutputManifestFile 3 | Import-Module -Force $ModuleOutputManifestFile 4 | } 5 | -------------------------------------------------------------------------------- /build/tasks/ImportPSModules.task.ps1: -------------------------------------------------------------------------------- 1 | Task ImportPSModules Initialize, ImportDependencyConfig, { 2 | 'Importing PowerShell Modules for phase {0}' -f $Phase 3 | if(-not $DependencyConfig.PowerShell) { 4 | 'No PowerShell dependencies found. Skipping.' 5 | return 6 | } 7 | 8 | foreach($Dependency in $DependencyConfig.PowerShell){ 9 | if($Dependency.Phases -notcontains $Phase) { 10 | "{0} is not required in this phase, skipping." -f $Dependency.Name 11 | continue 12 | } 13 | 'Importing module {0} version {1}' -f $Dependency.Name, $Dependency.Version 14 | Import-Module -Force -Name $Dependency.Name -RequiredVersion $Dependency.Version -scope Global 15 | } 16 | } -------------------------------------------------------------------------------- /build/tasks/Initialize.task.ps1: -------------------------------------------------------------------------------- 1 | Task Initialize { 2 | $Script:SrcPath = Join-Path $ProjectRoot 'src' 3 | $ModuleSrc = Get-ChildItem -Path $SrcPath -Directory 4 | $Script:ModuleSrcPath = $ModuleSrc.FullName 5 | $Script:ModuleName = $ModuleSrc.Name 6 | $Script:ModuleSrcManifestFile = Join-Path $ModuleSrcPath "$ModuleName.psd1" 7 | $Script:ModuleSrcRootModuleFile = Join-Path $ModuleSrcPath "$ModuleName.psm1" 8 | $Script:ModuleSrcPublicPath = Join-Path $ModuleSrcPath 'public' 9 | $Script:ModuleSrcPrivatePath = Join-Path $ModuleSrcPath 'private' 10 | $Script:ModuleSrcClassesPath = Join-Path $ModuleSrcPath 'classes' 11 | $Script:ModuleSrcEnumsPath = Join-Path $ModuleSrcPath 'enums' 12 | $Script:ModuleSrcAssetsPath = Join-Path $ModuleSrcPath 'assets' 13 | $Script:TestPath = Join-Path $ProjectRoot 'test' 14 | $Script:TestIntegrationPath = Join-Path $TestPath 'integration' 15 | $Script:TestUnitPath = Join-Path $TestPath 'unit' 16 | $Script:LocalPSModulePath = Join-Path $ProjectRoot 'psmodules' 17 | $Script:ConfigPath = Join-Path $ProjectRoot 'config' 18 | $Script:DependenciesFile = Join-Path $ConfigPath 'dependencies.json' 19 | 20 | if($OutputPath) { 21 | 'OutputPath derived from user input' 22 | } 23 | if(-not $OutputPath -and $env:Build_ArtifactStagingDirectory) { 24 | 'OutputPath derived from Build_ArtifactStagingDirectory' 25 | $Script:OutputPath = $env:Build_ArtifactStagingDirectory 26 | } 27 | if(-not $OutputPath) { 28 | 'OutputPath derived from ProjectRoot' 29 | $Script:OutputPath = Join-Path $ProjectRoot 'bin' 30 | } 31 | 32 | $Script:ModulesOutputPath = Join-Path $OutputPath 'modules' 33 | $Script:ModuleOutputPath = Join-Path $ModulesOutputPath $ModuleName 34 | $Script:ModuleOutputAssetsPath = Join-Path $ModuleOutputPath 'assets' 35 | $Script:ModuleOutputManifestFile = Join-Path $ModuleOutputPath "$ModuleName.psd1" 36 | $Script:ModuleOutputRootModuleFile = Join-Path $ModuleOutputPath "$ModuleName.psm1" 37 | 38 | $ModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator 39 | if($ModulePaths -NotContains $LocalPSModulePath) { 40 | $env:PSModulePath = '{0}{1}{2}' -f @( 41 | $env:PSModulePath 42 | [System.IO.Path]::PathSeparator 43 | $LocalPSModulePath 44 | ) 45 | } 46 | 47 | $Script:ProjectDirectories = @( 48 | $OutputPath 49 | $ModulesOutputPath 50 | $ModuleOutputPath 51 | $LocalPSModulePath 52 | ) 53 | 54 | ' ' 55 | 'ProjectRoot: {0}' -f $Script:ProjectRoot 56 | 'ModuleName: {0}' -f $Script:ModuleName 57 | 'SrcPath: {0}' -f $Script:SrcPath 58 | 'ModuleSrcPath: {0}' -f $Script:ModuleSrcPath 59 | 'ModuleSrcManifestFile: {0}' -f $Script:ModuleSrcManifestFile 60 | 'ModuleSrcRootModuleFile: {0}' -f $Script:ModuleSrcRootModuleFile 61 | 'ModuleSrcPublicPath: {0}' -f $Script:ModuleSrcPublicPath 62 | 'ModuleSrcPrivatePath: {0}' -f $Script:ModuleSrcPrivatePath 63 | 'ModuleSrcClassesPath: {0}' -f $Script:ModuleSrcClassesPath 64 | 'ModuleSrcEnumsPath: {0}' -f $Script:ModuleSrcEnumsPath 65 | 'ModuleSrcAssetsPath: {0}' -f $Script:ModuleSrcAssetsPath 66 | 'TestPath: {0}' -f $Script:TestPath 67 | 'TestIntegrationPath: {0}' -f $Script:TestIntegrationPath 68 | 'TestUnitPath: {0}' -f $Script:TestUnitPath 69 | 'OutputPath: {0}' -f $Script:OutputPath 70 | 'ModulesOutputPath: {0}' -f $Script:ModulesOutputPath 71 | 'ModuleOutputPath: {0}' -f $Script:ModuleOutputPath 72 | 'ModuleOutputAssetsPath: {0}' -f $Script:ModuleOutputAssetsPath 73 | 'ModuleOutputManifestFile: {0}' -f $Script:ModuleOutputManifestFile 74 | 'ModuleOutputRootModuleFile: {0}' -f $Script:ModuleOutputRootModuleFile 75 | 'LocalPSModulePath: {0}' -f $Script:LocalPSModulePath 76 | 'ConfigPath: {0}' -f $Script:ConfigPath 77 | 'DependenciesFile: {0}' -f $Script:DependenciesFile 78 | 'env:PSModulePath: {0}' -f $env:PSModulePath 79 | 'ProjectDirectories: {0}' -f ($Script:ProjectDirectories -join ', ') 80 | 'Phase: {0}' -f $Script:Phase 81 | 'PSGalleryApiKey present: {0}' -f (-not [string]::IsNullOrWhiteSpace($Script:PSGalleryApiKey)) 82 | 'PSRepositoryName: {0}' -f $Script:PSRepositoryName 83 | 'PSRepositoryUrl: {0}' -f $Script:PSRepositoryUrl 84 | 'PSRepositoryUrl: {0}' -f $Script:PSRepositoryUrl 85 | 'PSRepositoryAuthMethod: {0}' -f $Script:PSRepositoryAuthMethod 86 | 'PSRepositoryUser: {0}' -f $Script:PSRepositoryUser 87 | 'PSRepositoryPassword present: {0}' -f (-not [string]::IsNullOrWhiteSpace($Script:PSRepositoryPassword)) 88 | 'PSRepositoryApiKey present: {0}' -f (-not [string]::IsNullOrWhiteSpace($Script:PSRepositoryApiKey)) 89 | 'BuildVersion: {0}' -f $Script:BuildVersion 90 | } -------------------------------------------------------------------------------- /build/tasks/InstallDependencies.task.ps1: -------------------------------------------------------------------------------- 1 | Task InstallDependencies RegisterPSRepository, InstallDependenciesPS, ImportPSModules -------------------------------------------------------------------------------- /build/tasks/InstallDependenciesPS.task.ps1: -------------------------------------------------------------------------------- 1 | Task InstallDependenciesPS Initialize, CreatePaths, RegisterPSRepository, ImportDependencyConfig, { 2 | 'Installing PowerShell Dependencies for phase {0}' -f $Phase 3 | if(-not $DependencyConfig.PowerShell) { 4 | 'No PowerShell dependencies found. Skipping.' 5 | return 6 | } 7 | 8 | $AvailableModules = Get-Module -ListAvailable 9 | $Params = @{ 10 | Repository = 'PSGallery' 11 | Path = $LocalPSModulePath 12 | Force = $true 13 | AllowPrerelease = $true 14 | AcceptLicense = $true 15 | Verbose = $true 16 | } 17 | 18 | If($PSRepositoryName) { 19 | $Params['Repository'] = $PSRepositoryName, 'PSGallery' 20 | } 21 | 22 | if($PSRepositoryUser -and $PSRepositoryPassword) { 23 | 'PSRepositoryUser and PSRepositoryPassword present and will be used' 24 | $SecurePass = $PSRepositoryPassword | ConvertTo-SecureString -AsPlainText -Force 25 | $Params['Credential'] = [PSCredential]::new($PSRepositoryUser, $SecurePass) 26 | } 27 | 28 | foreach($Dependency in $DependencyConfig.PowerShell){ 29 | if($Dependency.Phases -notcontains $Phase) { 30 | "{0} is not required in this phase, skipping." -f $Dependency.Name 31 | continue 32 | } 33 | 'Checking for existence of module {0} version {1}' -f $Dependency.Name, $Dependency.Version 34 | $Installed = $AvailableModules.Where({$_.Name -eq $Dependency.Name -and $_.Version -eq $Dependency.Version}) 35 | if ($Installed) { 36 | 'module {0} version {1} already installed. Skipping.' -f $Dependency.Name, $Dependency.Version 37 | continue 38 | } 39 | Save-Module @Params -Name $Dependency.Name -RequiredVersion $Dependency.Version 40 | ' ' 41 | ' ' 42 | } 43 | } -------------------------------------------------------------------------------- /build/tasks/PublishPSModuleNuget.task.ps1: -------------------------------------------------------------------------------- 1 | Task PublishPSModuleNuget Initialize, ImportModule, { 2 | $Module = Get-Module -Name $ModuleName 3 | 'Publishing Module "{0}" Nupkg' -f $Module.Name 4 | $Script:PSModuleNupkg = $Module | Publish-PSModuleNuget -OutputPath $OutputPath -Verbose -PassThru | 5 | Foreach-Object -MemberName FullName 6 | 'PSModuleNupkg: {0}' -f $Script:PSModuleNupkg 7 | } 8 | -------------------------------------------------------------------------------- /build/tasks/RegisterPSRepository.task.ps1: -------------------------------------------------------------------------------- 1 | Task RegisterPSRepository -If {$Script:PSRepositoryName} Initialize, { 2 | 'Ensuring repository {0} at {1}' -f $PSRepositoryName, $PSRepositoryUrl 3 | $PSRepository = Get-PSRepository -Name $PSRepositoryName -ErrorAction SilentlyContinue 4 | 5 | if ($PSRepository -and $PSRepository.SourceLocation -eq $PSRepositoryUrl) { 6 | 'PSRepository {0} already exists. Skipping.' -f $PSRepositoryName 7 | return 8 | } 9 | 10 | if($PSRepository -and ($PSRepository.SourceLocation -ne $PSRepositoryUrl -or $PSRepository.InstallationPolicy -ne 'Trusted')) { 11 | 'PSRepository {0} has invalid SourceLocation {1}. expected {2}' -f @( 12 | $PSRepositoryName, 13 | $PSRepository.SourceLocation, 14 | $PSRepositoryUrl 15 | ) 16 | Unregister-PSRepository -Name $PSRepositoryName -Verbose 17 | } 18 | 19 | $Params = @{ 20 | Name = $PSRepositoryName 21 | SourceLocation = $PSRepositoryUrl 22 | PublishLocation = $PSRepositoryUrl 23 | InstallationPolicy = "Trusted" 24 | Verbose = $true 25 | } 26 | if($PSRepositoryUser -and $PSRepositoryPassword) { 27 | 'PSRepositoryUser and PSRepositoryPassword present and will be used for registration' 28 | $SecurePass = $PSRepositoryPassword | ConvertTo-SecureString -AsPlainText -Force 29 | $Params['Credential'] = [PSCredential]::new($PSRepositoryUser, $SecurePass) 30 | } 31 | 'Registering PSRepository {0} at {1}' -f $PSRepositoryName, $PSRepositoryUrl 32 | Register-PSRepository @Params 33 | } -------------------------------------------------------------------------------- /build/tasks/UpdateExportFunctionsAndAliases.task.ps1: -------------------------------------------------------------------------------- 1 | Task UpdateExportFunctionsAndAliases Initialize, InstallDependencies, { 2 | "Parsing {0} for Functions and Aliases" -f $ModuleSrcPublicPath 3 | $FunctionFiles = Get-ChildItem $ModuleSrcPublicPath -Filter '*.ps1' -Recurse | 4 | Where-Object { $_.Name -notmatch '\.tests{0,1}\.ps1' } 5 | $ExportFunctions = [System.Collections.Generic.HashSet[String]]::New([System.StringComparer]::InvariantCultureIgnoreCase) 6 | $ExportAliases = [System.Collections.Generic.HashSet[String]]::New([System.StringComparer]::InvariantCultureIgnoreCase) 7 | foreach ($FunctionFile in $FunctionFiles) { 8 | "- Processing $($FunctionFile.FullName)" 9 | $AST = [System.Management.Automation.Language.Parser]::ParseFile($FunctionFile.FullName, [ref]$null, [ref]$null) 10 | $Functions = $AST.FindAll( { 11 | $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] 12 | }, $true) 13 | foreach ($FunctionName in $Functions.Name) { 14 | ' Found Function {0}' -f $FunctionName 15 | if(-not $ExportFunctions.Add($FunctionName)) { 16 | throw "'$FunctionName' is a duplicate function." 17 | } 18 | } 19 | $Aliases = $AST.FindAll( { 20 | $args[0] -is [System.Management.Automation.Language.AttributeAst] -and 21 | $args[0].parent -is [System.Management.Automation.Language.ParamBlockAst] -and 22 | $args[0].TypeName.FullName -eq 'alias' 23 | }, $true) 24 | Foreach ($Alias in $Aliases.PositionalArguments.value) { 25 | ' Found Alias {0}' -f $Alias 26 | if(-not $ExportAliases.Add($Alias)) { 27 | throw "'$Alias' is a duplicate alias." 28 | } 29 | } 30 | } 31 | 32 | 'Updating FunctionsToExport in {0}' -f $ModuleOutputManifestFile 33 | Update-Metadata -Path $ModuleOutputManifestFile -PropertyName 'FunctionsToExport' -Value $ExportFunctions 34 | 'Updating AliasesToExport in {0}' -f $ModuleOutputManifestFile 35 | Update-Metadata -Path $ModuleOutputManifestFile -PropertyName 'AliasesToExport' -Value $ExportAliases 36 | } -------------------------------------------------------------------------------- /build/tasks/VersionBump.task.ps1: -------------------------------------------------------------------------------- 1 | Task VersionBump Initialize, InstallDependencies, { 2 | if(-Not $BuildVersion) { 3 | 'BuildVersion not present' 4 | 'Locating GitVersion binary' 5 | $command = Get-Command -Name 'gitversion' -CommandType 'Application' | 6 | Where-Object {$_.Version -eq '4.0.0.0'} | 7 | select-object -First 1 8 | Push-Location $ProjectRoot 9 | try { 10 | 'Executing GitVersion' 11 | $GtVersion = & $command | ConvertFrom-Json -ErrorAction 'Stop' 12 | } finally { 13 | Pop-Location 14 | } 15 | 16 | $Script:BuildVersion = $GtVersion.NuGetVersionV2 17 | } else { 18 | 'BuildVersion {0} was supplied' -f $BuildVersion 19 | } 20 | 21 | $NewVersion, $PreviewVersion = $BuildVersion -split '-' 22 | $PreviewVersion = -join $PreviewVersion 23 | 24 | $Manifest = Import-PowerShellDataFile -Path $ModuleOutputManifestFile 25 | $PreviousVersion = $Manifest.ModuleVersion 26 | 27 | If (-not $Manifest.PrivateData) { 28 | 'PrivateData was not found' 29 | $Manifest['PrivateData'] = @{} 30 | } 31 | 32 | 'Updating {0} with version {1}' -f $ModuleOutputManifestFile, $NewVersion 33 | Update-Metadata -Path $ModuleOutputManifestFile -PropertyName 'ModuleVersion' -Value $NewVersion 34 | 35 | if($PreviewVersion) { 36 | 'Updating {0} with Prerelease {1}' -f $ModuleOutputManifestFile, $PreviewVersion 37 | $Manifest.PrivateData['Prerelease'] = $PreviewVersion 38 | Update-Metadata -Path $ModuleOutputManifestFile -PropertyName 'PrivateData' -Value $Manifest.PrivateData 39 | } else { 40 | 'Remove Prerelease from {0}' -f 41 | $Manifest.PrivateData.Remove('Prerelease') 42 | Update-Metadata -Path $ModuleOutputManifestFile -PropertyName 'PrivateData' -Value $Manifest.PrivateData 43 | } 44 | 45 | $NewManifest = Import-PowerShellDataFile -Path $ModuleOutputManifestFile 46 | 47 | 'OldManifestVersion: {0}' -f $PreviousVersion 48 | 'BuildVersion: {0}' -f $BuildVersion 49 | 'NewVersion: {0}' -f $NewVersion 50 | 'NewManifestVersion: {0}' -f $NewManifest.ModuleVersion 51 | 'NewManifestPrerelease: {0}' -f $NewManifest.PrivateData.Prerelease 52 | } -------------------------------------------------------------------------------- /config/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "PowerShell": [ 3 | { 4 | "Name": "Configuration", 5 | "Version": "1.3.1", 6 | "Phases": ["Build"] 7 | } 8 | ], 9 | "choco": [ 10 | { 11 | "Name": "GitVersion.Portable", 12 | "Version": "4.0.0", 13 | "Phases": ["Build"] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /scripts/New-ModuleManifest.ps1: -------------------------------------------------------------------------------- 1 | $ModuleName = 'PSPublishHelper' 2 | $ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path 3 | $SrcFolder = Join-Path $ProjectRoot 'src' 4 | $ModuleSrcPath = Join-Path $SrcFolder $ModuleName 5 | $ModuleManifestFile = Join-Path $ModuleSrcPath "$ModuleName.psd1" 6 | $Params = @{ 7 | Path = $ModuleManifestFile 8 | Author = "Mark E. Kraus" 9 | CompanyName = "Mark E. Kraus" 10 | Copyright = "Copyright (c) Mark E. Kraus. All rights reserved." 11 | RootModule = "$ModuleName.psm1" 12 | ModuleVersion = "0.0.1" 13 | Description = 'A module to assist with "Build Once, Deploy Many" publishing paradigms for PowerShell' 14 | Tags = "Module","Builder","Publishing","Template","CI","CD","CI/CD" 15 | ProjectUri = "https://github.com/markekraus/$ModuleName" 16 | LicenseUri = "https://github.com/markekraus/$ModuleName/blob/master/LICENSE" 17 | FunctionsToExport = @() 18 | CmdletsToExport = @() 19 | VariablesToExport = @() 20 | AliasesToExport = @() 21 | } 22 | New-ModuleManifest @Params 23 | (Get-Content -Path $ModuleManifestFile) | ForEach-Object { 24 | if($_ -match '^#[^=]*$') {return} 25 | if($_ -match '^/s*$') {return} 26 | if($_ -match '^$') {return} 27 | if($_ -notmatch '^@\{|^\}$'){ 28 | " $_" 29 | } else { 30 | $_ 31 | } 32 | } | Set-Content -Encoding utf8 -Path $ModuleManifestFile -------------------------------------------------------------------------------- /src/PSPublishHelper/PSPublishHelper.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'PSPublishHelper.psm1' 3 | ModuleVersion = '0.0.1' 4 | # CompatiblePSEditions = @() 5 | GUID = '89d9bea3-6e06-44ef-8772-a45b644da8c0' 6 | Author = 'Mark E. Kraus' 7 | CompanyName = 'Mark E. Kraus' 8 | Copyright = 'Copyright (c) Mark E. Kraus. All rights reserved.' 9 | Description = 'A module to assist with "Build Once, Deploy Many" publishing paradigms for PowerShell' 10 | # PowerShellVersion = '' 11 | # PowerShellHostName = '' 12 | # PowerShellHostVersion = '' 13 | # DotNetFrameworkVersion = '' 14 | # CLRVersion = '' 15 | # ProcessorArchitecture = '' 16 | # RequiredModules = @() 17 | # RequiredAssemblies = @() 18 | # ScriptsToProcess = @() 19 | # TypesToProcess = @() 20 | # FormatsToProcess = @() 21 | # NestedModules = @() 22 | FunctionsToExport = @() 23 | CmdletsToExport = @() 24 | # VariablesToExport = @() 25 | AliasesToExport = @() 26 | # DscResourcesToExport = @() 27 | # ModuleList = @() 28 | # FileList = @() 29 | PrivateData = @{ 30 | PSData = @{ 31 | # Tags applied to this module. These help with module discovery in online galleries. 32 | Tags = 'Module', 'Builder', 'Publishing', 'Template', 'CI', 'CD', 'CI/CD' 33 | # A URL to the license for this module. 34 | LicenseUri = 'https://github.com/markekraus/PSPublishHelper/blob/master/LICENSE' 35 | # A URL to the main website for this project. 36 | ProjectUri = 'https://github.com/markekraus/PSPublishHelper' 37 | # A URL to an icon representing this module. 38 | # IconUri = '' 39 | # ReleaseNotes of this module 40 | # ReleaseNotes = '' 41 | } # End of PSData hashtable 42 | } # End of PrivateData hashtable 43 | # HelpInfoURI = '' 44 | # DefaultCommandPrefix = '' 45 | } 46 | -------------------------------------------------------------------------------- /src/PSPublishHelper/PSPublishHelper.psm1: -------------------------------------------------------------------------------- 1 | $Script:Nuspec = @" 2 | 3 | 4 | 5 | {0} 6 | {1} 7 | {2} 8 | {3} 9 | {4} 10 | {5} 11 | {6} 12 | {7} 13 | {8} 14 | {9} 15 | {10} 16 | {11} 17 | 18 | {12} 19 | 20 | 21 | 22 | "@ -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Copy-PSModule.ps1: -------------------------------------------------------------------------------- 1 | function Copy-PSModule { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory)] 5 | [ValidateNotNull()] 6 | [PSModuleInfo] 7 | $Module, 8 | 9 | [Parameter(Mandatory)] 10 | [ValidateNotNullOrEmpty()] 11 | [string] 12 | $Path 13 | ) 14 | end { 15 | $Params = @{ 16 | Path = $Path 17 | Recurse = $true 18 | Force = $true 19 | ErrorAction = 'SilentlyContinue' 20 | } 21 | Microsoft.PowerShell.Management\Remove-Item @Params 22 | $Params = @{ 23 | Path = $Path 24 | ItemType = 'Directory' 25 | Force = $true 26 | } 27 | $null = Microsoft.PowerShell.Management\New-Item @Params 28 | Microsoft.PowerShell.Management\Get-ChildItem $Module.ModuleBase -recurse | 29 | ForEach-Object { 30 | if ($_.PSIsContainer) { 31 | $Params = @{ 32 | Force = $true 33 | Confirm = $false 34 | WhatIf = $false 35 | Recurse = $true 36 | Container = $true 37 | Destination = $Path 38 | } 39 | $_ | Microsoft.PowerShell.Management\Copy-Item @Params 40 | } else { 41 | $Params = @{ 42 | Force = $true 43 | Confirm = $false 44 | WhatIf = $false 45 | Destination = $Path 46 | } 47 | $_ | Microsoft.PowerShell.Management\Copy-Item @Params 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Format-PSModuleDependency.ps1: -------------------------------------------------------------------------------- 1 | function Format-PSModuleDependency { 2 | [OutputType([string])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(ValueFromPipeline)] 6 | [Microsoft.PowerShell.Commands.ModuleSpecification] 7 | $Dependency 8 | ) 9 | process { 10 | if (-not $Dependency) { 11 | return 12 | } 13 | $Version = [string]::Empty 14 | # Version format in NuSpec: 15 | # "[2.0]" --> (== 2.0) Required Version 16 | # "2.0" --> (>= 2.0) Minimum Version 17 | # 18 | # When only MaximumVersion is specified in the ModuleSpecification 19 | # (,1.0] = x <= 1.0 20 | # 21 | # When both Minimum and Maximum versions are specified in the ModuleSpecification 22 | # [1.0,2.0] = 1.0 <= x <= 2.0 23 | if($Dependency.RequiredVersion) { 24 | $Version = "[{0}]" -f $Dependency.RequiredVersion 25 | } elseif($Dependency.Version -and $Dependency.MaximumVersion) { 26 | $Version = "[{0},{1}]" -f $Dependency.Version, $Dependency.MaximumVersion 27 | } elseif($Dependency.MaximumVersion) { 28 | $Version = "(,{0}]" -f $Dependency.MaximumVersion 29 | } elseif($Dependency.Version) { 30 | $Version = "{0}" -f $Dependency.Version 31 | } 32 | 33 | if ([System.string]::IsNullOrWhiteSpace($Version)) { 34 | "" -f $Dependency.Name 35 | } else { 36 | "" -f $Dependency.Name, $Version 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-AvailableRoleCapabilityName.ps1: -------------------------------------------------------------------------------- 1 | function Get-AvailableRoleCapabilityName { 2 | [CmdletBinding()] 3 | Param ( 4 | [Parameter(Mandatory=$true)] 5 | [ValidateNotNullOrEmpty()] 6 | [PSModuleInfo] 7 | $Module 8 | ) 9 | 10 | $RoleCapabilitiesDir = Join-Path $Module.ModuleBase 'RoleCapabilities' 11 | if(Microsoft.PowerShell.Management\Test-Path -Path $RoleCapabilitiesDir -PathType Container) 12 | { 13 | $Params = @{ 14 | Path = $RoleCapabilitiesDir 15 | Filter = '*.psrc' 16 | } 17 | Microsoft.PowerShell.Management\Get-ChildItem @Params | 18 | ForEach-Object -MemberName BaseName 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-EscapedString.ps1: -------------------------------------------------------------------------------- 1 | function Get-EscapedString 2 | { 3 | [CmdletBinding()] 4 | [OutputType([String])] 5 | Param ( 6 | [Parameter(ValueFromPipeline)] 7 | [string] 8 | $Value 9 | ) 10 | 11 | process { 12 | [System.Security.SecurityElement]::Escape($Value) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-ExportedDscResources.ps1: -------------------------------------------------------------------------------- 1 | function Get-ExportedDscResources { 2 | [CmdletBinding()] 3 | Param( 4 | [Parameter(Mandatory=$true)] 5 | [ValidateNotNullOrEmpty()] 6 | [PSModuleInfo] 7 | $Module 8 | ) 9 | 10 | $Params = @{ 11 | Name = 'Get-DscResource' 12 | Module = 'PSDesiredStateConfiguration' 13 | ErrorAction = 'Ignore' 14 | } 15 | if(-not $script:IsCoreCLR -and (Get-Command @Params)) { 16 | $OldPSModulePath = $env:PSModulePath 17 | 18 | try { 19 | $env:PSModulePath = Join-Path -Path $PSHOME -ChildPath "Modules" 20 | $ModuleBaseParent = Split-Path -Path $Module.ModuleBase -Parent 21 | $env:PSModulePath = "{0}{1}{2}" -f @( 22 | $env:PSModulePath, 23 | [System.IO.Path]::PathSeparator, 24 | $ModuleBaseParent 25 | ) 26 | 27 | $Params = @{ 28 | ErrorAction = 'SilentlyContinue' 29 | WarningAction = 'SilentlyContinue' 30 | } 31 | PSDesiredStateConfiguration\Get-DscResource @Params | 32 | Microsoft.PowerShell.Core\ForEach-Object { 33 | if( 34 | $_.Module -and 35 | $_.Module.Name -eq $Module.Name 36 | ){ 37 | $_.Name 38 | } 39 | } 40 | } finally { 41 | $env:PSModulePath = $OldPSModulePath 42 | } 43 | } else { 44 | $dscResourcesDir = Join-Path $Module.ModuleBase "DscResources" 45 | if(Microsoft.PowerShell.Management\Test-Path $dscResourcesDir) { 46 | Microsoft.PowerShell.Management\Get-ChildItem -Path $dscResourcesDir -Directory -Name 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-NupkgFilePath.ps1: -------------------------------------------------------------------------------- 1 | function Get-NupkgFilePath { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory)] 5 | [ValidateNotNull()] 6 | [PSModuleInfo] 7 | $Module, 8 | 9 | [Parameter(Mandatory)] 10 | [ValidateNotNullOrEmpty()] 11 | [string] 12 | $Path 13 | ) 14 | end { 15 | $PSData = Resolve-PSData -Module $Module 16 | $Version = Resolve-PSModuleVersion -Module $Module -PSData $PSData 17 | $FileName = '{0}.{1}.nupkg' -f $Module.Name, $Version 18 | Join-Path $Path $FileName 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-NuspecContents.ps1: -------------------------------------------------------------------------------- 1 | function Get-NuspecContents { 2 | [OutputType([String])] 3 | [CmdletBinding()] 4 | param ( 5 | [PSModuleInfo] 6 | $Module, 7 | 8 | [Hashtable] 9 | $PSData 10 | ) 11 | end { 12 | 13 | $VersionText = Resolve-PSModuleVersion -Module $Module -PSData $PSData 14 | 15 | $TagsText = Resolve-PSModuleTags -Module $Module -Tags $PSData.Tags 16 | 17 | $RequireLicenseAcceptanceText = ([bool]$PSData.RequireLicenseAcceptance).ToString().ToLower() 18 | 19 | $Description = $Module.Description 20 | if ([string]::IsNullOrWhiteSpace($Description)) { 21 | $Description = $Module.Name 22 | } 23 | 24 | $ArgumentList = [System.Collections.Generic.List[string]]::new() 25 | @( 26 | $Module.Name, 27 | $VersionText, 28 | $Module.Author, 29 | $Module.CompanyName, 30 | $Description, 31 | $Module.ReleaseNotes, 32 | $RequireLicenseAcceptanceText, 33 | $Module.Copyright, 34 | $TagsText 35 | ) | Get-EscapedString | ForEach-Object { 36 | $ArgumentList.Add($_) 37 | } 38 | 39 | $LicenseUriText = if ($PSData.LicenseUri) { 40 | '{0}' -f ($PSData.LicenseUri | Get-EscapedString) 41 | } 42 | $ArgumentList.Add($LicenseUriText) 43 | 44 | $ProjectUriText = if ($PSData.ProjectUri) { 45 | '{0}' -f ($PSData.ProjectUri | Get-EscapedString) 46 | } 47 | $ArgumentList.Add($ProjectUriText) 48 | 49 | $IconUriText = if ($PSData.IconUri) { 50 | '{0}' -f ($PSData.IconUri | Get-EscapedString) 51 | } 52 | $ArgumentList.Add($IconUriText) 53 | 54 | $DependencyText = @( 55 | $Module | Resolve-PSModuleDependency -ExternalModuleDependencies $PSData.ExternalModuleDependencies | Format-PSModuleDependency 56 | ) -Join ([Environment]::NewLine) 57 | $ArgumentList.Add($DependencyText) 58 | 59 | $Script:Nuspec -f $ArgumentList.ToArray() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-NuspecFilePath.ps1: -------------------------------------------------------------------------------- 1 | function Get-NuspecFilePath { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory)] 5 | [ValidateNotNull()] 6 | [PSModuleInfo] 7 | $Module, 8 | 9 | [Parameter(Mandatory)] 10 | [ValidateNotNullOrEmpty()] 11 | [string] 12 | $Path 13 | ) 14 | end { 15 | $FileName = '{0}.nuspec' -f $Module.Name 16 | Join-Path $Path $FileName 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-PSModuleManifestData.ps1: -------------------------------------------------------------------------------- 1 | function Get-PSModuleManifestData { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory,ValueFromPipeline)] 5 | [ValidateNotNull()] 6 | [PSModuleInfo] 7 | $Module 8 | ) 9 | process { 10 | if ($Module.Path -match '\.psd1$') { 11 | $Manifest = $Module.Path 12 | } else { 13 | $ManifestName = '{0}.psd1' -f $Module.Name 14 | $Manifest = Join-Path $Module.ModuleBase $ManifestName 15 | } 16 | Import-PowerShellDataFile -Path $Manifest 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Get-TemporaryPath.ps1: -------------------------------------------------------------------------------- 1 | function Get-TemporaryPath { 2 | [OutputType([string])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(Mandatory,ValueFromPipeline)] 6 | [ValidateNotNull()] 7 | [PSModuleInfo] 8 | $Module 9 | ) 10 | process { 11 | $Base = Join-Path ([System.IO.Path]::GetTempPath()) (Get-Random) 12 | Join-Path $Base $Module.Name 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-NugetCommand.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-NugetCommand { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter()] 5 | [System.Management.Automation.PSCmdlet] 6 | $Cmdlet 7 | ) 8 | end { 9 | $ErrorCmdlet = $Cmdlet 10 | if (-not $Cmdlet) { 11 | $ErrorCmdlet = $PSCmdlet 12 | } 13 | $Params = @{ 14 | Name = 'nuget' 15 | CommandType = 'Application' 16 | ErrorAction = 'SilentlyContinue' 17 | } 18 | $Nuget = Get-Command @Params | Select-Object -First 1 19 | if(-not $Nuget) { 20 | $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( 21 | [System.Management.Automation.ItemNotFoundException]::new( 22 | "Unable to find nuget binary. Nuget is required" 23 | ), 24 | "NugetNotFound", 25 | [System.Management.Automation.ErrorCategory]::NotInstalled, 26 | $null 27 | ) 28 | $ErrorCmdlet.ThrowTerminatingError($ErrorRecord) 29 | } 30 | $Nuget 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-PSData.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-PSData { 2 | [OutputType([Hashtable])] 3 | [CmdletBinding()] 4 | param ( 5 | [PSModuleInfo] 6 | $Module, 7 | 8 | [string] 9 | $ReleaseNotes, 10 | 11 | [string[]] 12 | $Tags, 13 | 14 | [Uri] 15 | $LicenseUri, 16 | 17 | [Uri] 18 | $IconUri, 19 | 20 | [Uri] 21 | $ProjectUri 22 | ) 23 | end { 24 | $Result = @{} 25 | $PSData = @{ 26 | ReleaseNotes = $ReleaseNotes 27 | Tags = $Tags 28 | LicenseUri = $LicenseUri 29 | IconUri = $IconUri 30 | ProjectUri = $ProjectUri 31 | Prerelease = $null 32 | RequireLicenseAcceptance = $null 33 | ExternalModuleDependencies = @() 34 | } 35 | foreach ($item in $PSData.GetEnumerator()) { 36 | $key = $item.key 37 | $Result[$key] = $item.value 38 | if (-not $item.value -and $key -ne 'Prerelease') { 39 | $Result[$key] = $Module.PrivateData.PSdata.$key 40 | } elseif (-not $item.value -and $key -eq 'Prerelease') { 41 | $Result[$key] = $Module.PrivateData.PSData.$key 42 | } 43 | Write-Verbose "$($key): $($Result[$key])" 44 | } 45 | $Result 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-PSModule.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-PSModule { 2 | [OutputType([System.Management.Automation.PSModuleInfo])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter()] 6 | [System.Management.Automation.PSCmdlet] 7 | $Cmdlet, 8 | 9 | [Parameter()] 10 | [string] 11 | $Name, 12 | 13 | [Parameter()] 14 | [string] 15 | $RequiredVersion 16 | ) 17 | end { 18 | if($Cmdlet) { 19 | $ErrorCmdlet = $Cmdlet 20 | } else { 21 | $ErrorCmdlet = $PSCmdlet 22 | } 23 | 24 | $Version, $Prerelease = $RequiredVersion -split '-', 2 25 | 26 | $Filter = { 27 | $_.Version.ToString() -eq $Version -and 28 | ( 29 | $_.PrivateData.PSData.Prerelease -eq $Prerelease -or 30 | $_.PrivateData.PSData.Prerelease -eq "-$Prerelease" 31 | ) 32 | } 33 | 34 | $Result = Get-Module -ListAvailable -Name $Name | 35 | Where-Object -FilterScript $Filter | 36 | Select-Object -First 1 37 | 38 | if (-not $Result) { 39 | $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( 40 | [System.Management.Automation.ItemNotFoundException]::new( 41 | "Unable to find Module $Name Version $RequiredVersion" 42 | ), 43 | "ModuleNotFound", 44 | [System.Management.Automation.ErrorCategory]::InvalidArgument, 45 | $null 46 | ) 47 | $ErrorCmdlet.ThrowTerminatingError($ErrorRecord) 48 | } 49 | $Result 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-PSModuleDependency.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-PSModuleDependency { 2 | [OutputType([Microsoft.PowerShell.Commands.ModuleSpecification])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(Mandatory, ValueFromPipeline)] 6 | [PSModuleInfo] 7 | $Module, 8 | 9 | [String[]] 10 | $ExternalModuleDependencies 11 | ) 12 | process { 13 | # The manifest contains the actual ModuleSpec used for Version requirements 14 | $ModuleData = $Module | Get-PSModuleManifestData 15 | 16 | # A module in RequiredModules is not a dependency if it's listed in ExternalModuleDependencies 17 | $ModuleData.RequiredModules | Resolve-PSModuleInfo | Where-Object { 18 | $_.Name -notin $ExternalModuleDependencies 19 | } 20 | 21 | $NestModuleInfo = $ModuleData.NestedModules | Resolve-PSModuleInfo 22 | 23 | # A module in NestedModules become a dependency 24 | # when it is not not packaged with the module 25 | $NestedDependencies = $Module.NestedModules | Where-Object { 26 | -not $_.ModuleBase.StartsWith($Module.ModuleBase, [System.StringComparison]::OrdinalIgnoreCase) -or 27 | -not $_.Path -or 28 | -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $_.Path) 29 | } | Foreach-Object -MemberName Name 30 | 31 | $NestModuleInfo | Where-Object {$_.Name -in $NestedDependencies} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-PSModuleInfo.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-PSModuleInfo { 2 | [OutputType([Microsoft.PowerShell.Commands.ModuleSpecification])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(ValueFromPipeline)] 6 | [PSObject] 7 | $InputObject 8 | ) 9 | 10 | process { 11 | # '*' can be specified in the MaximumVersion of a ModuleSpecification to convey 12 | # that maximum possible value of that version part. 13 | # like 1.0.0.* --> 1.0.0.99999999 14 | if($InputObject.MaximumVersion){ 15 | $InputObject.MaximumVersion = $InputObject.MaximumVersion -replace '\*','99999999' 16 | } 17 | 18 | [Microsoft.PowerShell.Commands.ModuleSpecification]$InputObject 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-PSModuleTags.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-PSModuleTags { 2 | [CmdletBinding()] 3 | param ( 4 | [PSModuleInfo] 5 | $Module, 6 | 7 | [String[]] 8 | $Tags 9 | ) 10 | end { 11 | $TagsList = [System.Collections.Generic.HashSet[String]]::new() 12 | foreach($Tag in $Tags) { 13 | $null = $TagsList.Add($Tag) 14 | } 15 | 16 | $null = $TagsList.add('PSModule') 17 | 18 | foreach($Cmdlet in $Module.ExportedCmdlets.Keys) { 19 | $null = $TagsList.Add('PSIncludes_Cmdlet') 20 | $null = $TagsList.add(('PSCmdlet_{0}' -f $Cmdlet)) 21 | } 22 | 23 | foreach($Func in $Module.ExportedFunctions.Keys) { 24 | $null = $TagsList.Add('PSIncludes_Function') 25 | $null = $TagsList.add(('PSFunction_{0}' -f $func)) 26 | } 27 | 28 | foreach($Command in $Module.ExportedCommands.Keys) { 29 | $null = $TagsList.add(('PSCommand_{0}' -f $Command)) 30 | } 31 | 32 | if(!$IsCoreCLR) { 33 | foreach ($DscResource in (Get-ExportedDscResources -Module $Module -ErrorAction SilentlyContinue)) { 34 | $null = $TagsList.Add('PSIncludes_DscResource') 35 | $null = $TagsList.add(('PSDscResource_{0}' -f $DscResource)) 36 | } 37 | } else { 38 | Write-Verbose 'Skipping DSC resource enumeration as it is not supported on PS Core.' 39 | Write-Verbose 'Please use Windows PowerShell to build DSC modules.' 40 | } 41 | 42 | 43 | foreach ($Role in (Get-AvailableRoleCapabilityName -Module $Module)) { 44 | $null = $TagsList.Add('PSIncludes_RoleCapability') 45 | $null = $TagsList.add(('PSRoleCapability_{0}' -f $Role)) 46 | } 47 | 48 | $TagsList -join ' ' 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/PSPublishHelper/private/Resolve-PSModuleVersion.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-PSModuleVersion { 2 | [OutputType([string])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter()] 6 | [PsModuleInfo] 7 | $Module, 8 | 9 | [Parameter()] 10 | [Hashtable] 11 | $PSData 12 | ) 13 | end { 14 | $Version = [string]$Module.Version 15 | if($PSData.Prerelease) { 16 | $Prerelease = $PSData.Prerelease 17 | if($Prerelease.StartsWith('-')) { 18 | $version = '{0}{1}' -f $Version, $Prerelease 19 | } else { 20 | $version = '{0}-{1}' -f $Version, $Prerelease 21 | } 22 | } 23 | $Version 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/PSPublishHelper/public/Publish-PSModuleNuget.ps1: -------------------------------------------------------------------------------- 1 | function Publish-PSModuleNuget { 2 | [CmdletBinding(DefaultParameterSetName = 'NameAndVersion')] 3 | param ( 4 | [Parameter( 5 | Mandatory, 6 | ParameterSetName = 'NameAndVersion' 7 | )] 8 | [ValidateNotNullOrEmpty()] 9 | [string] 10 | $Name, 11 | 12 | [Parameter( 13 | Mandatory, 14 | ParameterSetName = 'NameAndVersion' 15 | )] 16 | [ValidateNotNullOrEmpty()] 17 | [string] 18 | $RequiredVersion, 19 | 20 | [Parameter( 21 | Mandatory, 22 | ParameterSetName = 'PSModuleInfo', 23 | ValueFromPipeline 24 | )] 25 | [ValidateNotNull()] 26 | [PSModuleInfo] 27 | $InputObject, 28 | 29 | [Parameter( 30 | Mandatory, 31 | ParameterSetName = 'NameAndVersion' 32 | )] 33 | [Parameter( 34 | Mandatory, 35 | ParameterSetName = 'PSModuleInfo' 36 | )] 37 | [ValidateNotNullOrEmpty()] 38 | [ValidateScript({ 39 | if(-not (Test-Path -PathType Container -Path $_)){throw} 40 | else {$true} 41 | })] 42 | [string] 43 | $OutputPath, 44 | 45 | [Parameter(Mandatory = $false, ParameterSetName = 'PSModuleInfo')] 46 | [Parameter(Mandatory = $false, ParameterSetName = 'NameAndVersion')] 47 | [string] 48 | $ReleaseNotes, 49 | 50 | [Parameter(Mandatory = $false, ParameterSetName = 'PSModuleInfo')] 51 | [Parameter(Mandatory = $false, ParameterSetName = 'NameAndVersion')] 52 | [string[]] 53 | $Tags, 54 | 55 | [Parameter(Mandatory = $false, ParameterSetName = 'PSModuleInfo')] 56 | [Parameter(Mandatory = $false, ParameterSetName = 'NameAndVersion')] 57 | [Uri] 58 | $LicenseUri, 59 | 60 | [Parameter(Mandatory = $false, ParameterSetName = 'PSModuleInfo')] 61 | [Parameter(Mandatory = $false, ParameterSetName = 'NameAndVersion')] 62 | [Uri] 63 | $IconUri, 64 | 65 | [Parameter(Mandatory = $false, ParameterSetName = 'PSModuleInfo')] 66 | [Parameter(Mandatory = $false, ParameterSetName = 'NameAndVersion')] 67 | [Uri] 68 | $ProjectUri, 69 | 70 | [Parameter(Mandatory = $false, ParameterSetName = 'PSModuleInfo')] 71 | [Parameter(Mandatory = $false, ParameterSetName = 'NameAndVersion')] 72 | [Switch] 73 | $PassThru 74 | ) 75 | 76 | begin { 77 | $Nuget = Resolve-NugetCommand -Cmdlet $PSCmdlet -ErrorAction Stop 78 | Write-Verbose "Nuget found at $($Nuget.Path)" 79 | $OutputPath = Convert-Path $OutputPath -ErrorAction Stop 80 | Write-Verbose "Output Path: $OutputPath" 81 | } 82 | 83 | process { 84 | if($PSCmdlet.ParameterSetName -eq 'NameAndVersion') { 85 | $Params = @{ 86 | Cmdlet = $PSCmdlet 87 | Name = $Name 88 | RequiredVersion = $RequiredVersion 89 | ErrorAction = 'stop' 90 | } 91 | $InputObject = Resolve-PSModule @Params 92 | } 93 | 94 | Write-Verbose "Using Module from $($InputObject.ModuleBase)" 95 | 96 | $Params = @{ 97 | IconUri = $IconUri 98 | ReleaseNotes = $ReleaseNotes 99 | Module = $InputObject 100 | Tags = $Tags 101 | ProjectUri = $ProjectUri 102 | LicenseUri = $LicenseUri 103 | } 104 | $PSData = Resolve-PSData @Params 105 | 106 | $Params = @{ 107 | Module = $InputObject 108 | PSData = $PSData 109 | ErrorAction = 'stop' 110 | } 111 | $NuspecContents = Get-NuspecContents @Params 112 | 113 | $TempPath = $InputObject | Get-TemporaryPath 114 | try { 115 | Copy-PSModule -Module $InputObject -Path $TempPath -ErrorAction Stop 116 | $NuspecPath = Get-NuspecFilePath -Module $InputObject -Path $TempPath -ErrorAction Stop 117 | $NuspecContents | Set-Content -Path $NuspecPath -Force -Confirm:$false -WhatIf:$false -ErrorAction Stop 118 | $NupkgFilePath = Get-NupkgFilePath -Module $InputObject -Path $OutputPath -ErrorAction Stop 119 | Push-Location -StackName PSPublishHelperNugetPack -Path $TempPath -ErrorAction Stop 120 | $Output = & $Nuget pack $NuspecPath -OutputDirectory $OutputPath -BasePath $TempPath -Verbosity detailed -NonInteractive -NoDefaultExcludes 121 | Write-Verbose "Nuget Pack Output:" 122 | foreach($Line in $Output) { 123 | if($Line -notmatch 'NU5110|NU5111'){ 124 | Write-Verbose $Line 125 | } 126 | } 127 | } 128 | finally { 129 | Pop-Location -StackName PSPublishHelperNugetPack 130 | $Params = @{ 131 | Path = Split-path -Parent $TempPath 132 | Recurse = $true 133 | Force = $true 134 | ErrorAction = 'SilentlyContinue' 135 | } 136 | Microsoft.PowerShell.Management\Remove-Item @Params 137 | } 138 | 139 | if($PassThru) { 140 | Get-item -Path $NupkgFilePath -ErrorAction SilentlyContinue 141 | } 142 | } 143 | } 144 | --------------------------------------------------------------------------------