Lib/Module.ps1
function GetModule { <# .SYNOPSIS Tests whether an exising PowerShell module meets the minimum or required version #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name ) process { WriteVerbose -Message ($localized.LocatingModule -f $Name); ## Only return modules in the %ProgramFiles%\WindowsPowerShell\Modules location, ignore other $env:PSModulePaths $programFiles = [System.Environment]::GetFolderPath('ProgramFiles'); $modulesPath = ('{0}\WindowsPowerShell\Modules' -f $programFiles).Replace('\','\\'); $module = Get-Module -Name $Name -ListAvailable -Verbose:$false | Where-Object Path -match $modulesPath; if (-not $module) { WriteVerbose -Message ($localized.ModuleNotFound -f $Name); } else { WriteVerbose -Message ($localized.ModuleFoundInPath -f $module.Path); } return $module; } #end process } #end function GetModule #> function TestModuleVersion { <# .SYNOPSIS Tests whether an exising PowerShell module meets the minimum or required version #> [CmdletBinding()] [OutputType([System.Boolean])] param ( ## Path to the module's manifest file [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $ModulePath, ## The minimum version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'MinimumVersion')] [ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()] [System.Version] $MinimumVersion, ## The exact version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'RequiredVersion')] [ValidateNotNullOrEmpty()] [System.Version] $RequiredVersion, ## Catch all to be able to pass parameters via $PSBoundParameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) process { try { WriteVerbose -Message ($localized.QueryingModuleVersion -f [System.IO.Path]::GetFileNameWithoutExtension($ModulePath)); #$moduleManifest = Test-ModuleManifest -Path $ModulePath -Verbose:$false; $moduleManifest = ConvertToConfigurationData -ConfigurationData $ModulePath; WriteVerbose -Message ($localized.ExistingModuleVersion -f $moduleManifest.ModuleVersion); } catch { Write-Error "Oops $ModulePath" } if ($PSCmdlet.ParameterSetName -eq 'MinimumVersion') { return (($moduleManifest.ModuleVersion -as [System.Version]) -ge $MinimumVersion); } elseif ($PSCmdlet.ParameterSetName -eq 'RequiredVersion') { return (($moduleManifest.ModuleVersion -as [System.Version]) -eq $RequiredVersion); } } #end process } #end function TestModuleVersion function TestModule { <# .SYNOPSIS Tests whether an exising PowerShell module meets the minimum or required version #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## The minimum version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'MinimumVersion')] [ValidateNotNullOrEmpty()] [System.Version] $MinimumVersion, ## The exact version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'RequiredVersion')] [ValidateNotNullOrEmpty()] [System.Version] $RequiredVersion, ## Catch all to be able to pass parameters via $PSBoundParameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) process { $module = GetModule -Name $Name; if ($module) { $testModuleVersionParams = @{ ModulePath = $module.Path; } if ($MinimumVersion) { $testModuleVersionParams['MinimumVersion'] = $MinimumVersion; } if ($RequiredVersion) { $testModuleVersionParams['RequiredVersion'] = $RequiredVersion; } return (TestModuleVersion @testModuleVersionParams); } else { return $false; } } #end process } #end function TestModule function ResolveModule { <# .SYNOPSIS Resolves a lab module definition by its name from Lability configuration data. #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, [Parameter(Mandatory)] [ValidateSet('Module','DscResource')] [System.String] $ModuleType, ## Lab module name/ID [Parameter(ValueFromPipelineByPropertyName)] [System.String[]] $Name, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $ThrowIfNotFound ) process { $modules = $ConfigurationData.NonNodeData.($labDefaults.ModuleName).$ModuleType; if (($PSBoundParameters.ContainsKey('Name')) -and ($Name -notcontains '*')) { ## Check we have them all first.. foreach ($moduleName in $Name) { if ($modules.Name -notcontains $moduleName) { if ($ThrowIfNotFound) { throw ($localized.CannotResolveModuleNameError -f $ModuleType, $moduleName); } else { WriteWarning -Message ($localized.CannotResolveModuleNameError -f $ModuleType, $moduleName); } } } $modules = $modules | Where-Object { $_.Name -in $Name }; } return $modules; } } #end function ResolveLabResource function GetModuleCache { <# .SYNOPSIS Returns the requested cached PowerShell module zip [System.IO.FileInfo] object. .NOTES File system modules are not stored in the module cache. #> [CmdletBinding(DefaultParameterSetName = 'Name')] [OutputType([System.IO.FileInfo])] param ( ## PowerShell module/DSC resource module name [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Name, ## The minimum version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [ValidateNotNullOrEmpty()] [System.Version] $MinimumVersion, ## The exact version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.Version] $RequiredVersion, ## GitHub repository owner [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Owner, ## GitHub repository branch [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Branch, ## Source Filesystem module path [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Path, ## Provider used to download the module [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateSet('PSGallery','GitHub','FileSystem')] [System.String] $Provider, ## Lability PowerShell module info hashtable [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Module')] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable] $Module, ## Catch all to be able to pass parameter via $PSBoundParameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) begin { if ([System.String]::IsNullOrEmpty($Provider)) { $Provider = 'PSGallery' } if ($PSCmdlet.ParameterSetName -eq 'Module') { if ([System.String]::IsNullOrEmpty($Module.Name)) { throw ($localized.RequiredModuleParameterError -f 'Name'); } $Name = $Module.Name if (-not [System.String]::IsNullOrEmpty($Module.Provider)) { $Provider = $Module.Provider; } if (-not [System.String]::IsNullOrEmpty($Module.MinimumVersion)) { $MinimumVersion = $Module.MinimumVersion; } if (-not [System.String]::IsNullOrEmpty($Module.RequiredVersion)) { $RequiredVersion = $Module.RequiredVersion; } if (-not [System.String]::IsNullOrEmpty($Module.Owner)) { $Owner = $Module.Owner; } if (-not [System.String]::IsNullOrEmpty($Module.Branch)) { $Branch = $Module.Branch; } if (-not [System.String]::IsNullOrEmpty($Module.Path)) { $Path = $Module.Path; } } #end if Module if ($Provider -eq 'GitHub') { if ([System.String]::IsNullOrEmpty($Owner)) { throw ($localized.RequiredModuleParameterError -f 'Owner'); } ## Default to master branch if none specified if ([System.String]::IsNullOrEmpty($Branch)) { $Branch = 'master'; } } #end if GitHub elseif ($Provider -eq 'FileSystem') { if ([System.String]::IsNullOrEmpty($Path)) { throw ($localized.RequiredModuleParameterError -f 'Path'); } elseif (-not (Test-Path -Path $Path)) { throw ($localized.InvalidPathError -f 'Module', $Path); } else { ## If we have a file, ensure it's a .Zip file $fileSystemInfo = Get-Item -Path $Path; if ($fileSystemInfo -is [System.IO.FileInfo]) { if ($fileSystemInfo.Extension -ne '.zip') { throw ($localized.InvalidModulePathExtensionError -f $Path); } } } } #end if FileSystem } process { $moduleCachePath = (GetConfigurationData -Configuration Host).ModuleCachePath; ## If no provider specified, default to the PSGallery if (([System.String]::IsNullOrEmpty($Provider)) -or ($Provider -eq 'PSGallery')) { ## PowerShell Gallery modules are just suffixed with -v<Version>.zip $moduleRegex = '^{0}-v.+\.zip$' -f $Name; } elseif ($Provider -eq 'GitHub') { ## GitHub modules are suffixed with -v<Version>_<Owner>_<Branch>.zip $moduleRegex = '^{0}(-v.+)?_{1}_{2}\.zip$' -f $Name, $Owner, $Branch; } Write-Debug -Message ("Searching for files matching pattern '$moduleRegex'."); if ($Provider -in 'FileSystem') { ## We have a directory or a .zip file, so just return this return (Get-Item -Path $Path); } elseif ($Provider -in 'PSGallery','GitHub') { $modules = Get-ChildItem -Path $moduleCachePath -ErrorAction SilentlyContinue | Where-Object Name -match $moduleRegex | ForEach-Object { Write-Debug -Message ("Discovered file '$($_.FullName)'."); $trimStart = '{0}-v' -f $Name; $moduleVersionString = $PSItem.Name.TrimStart($trimStart); $moduleVersionString = $moduleVersionString -replace '(_\S+_\S+)?\.zip', ''; ## If we have no version number, default to the lowest version if ([System.String]::IsNullOrEmpty($moduleVersionString)) { $moduleVersionString = '0.0'; } $discoveredModule = [PSCustomObject] @{ Name = $Name; Version = $moduleVersionString -as [System.Version]; FileInfo = $PSItem; } Write-Output -InputObject $discoveredModule; } } if ($null -ne $RequiredVersion) { Write-Debug -Message ("Checking for modules that match version '$RequiredVersion'."); Write-Output -InputObject ( $modules | Where-Object { $_.Version -eq $RequiredVersion } | Select-Object -ExpandProperty FileInfo); } elseif ($null -ne $MinimumVersion) { Write-Debug -Message ("Checking for modules with a minimum version of '$MinimumVersion'."); Write-Output -InputObject ( $modules | Where-Object Version -ge $MinimumVersion | Sort-Object -Property Version | Select-Object -Last 1 -ExpandProperty FileInfo); } else { Write-Debug -Message ("Checking for the latest module version."); Write-Output -InputObject ( $modules | Sort-Object -Property Version | Select-Object -Last 1 -ExpandProperty FileInfo); } } #end process } #end function GetModuleCache function TestModuleCache { <# .SYNOPSIS Tests whether the requested PowerShell module is cached. #> [CmdletBinding(DefaultParameterSetName = 'Name')] [OutputType([System.Boolean])] param ( ## PowerShell module/DSC resource module name [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Name, ## The minimum version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [ValidateNotNullOrEmpty()] [System.Version] $MinimumVersion, ## The exact version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.Version] $RequiredVersion, ## GitHub repository owner [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Owner, ## GitHub repository branch [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Branch, ## Source Filesystem module path [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Path, ## Provider used to download the module [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateSet('PSGallery','GitHub','FileSystem')] [System.String] $Provider, ## Lability PowerShell module info hashtable [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Module')] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable] $Module, ## Catch all to be able to pass parameter via $PSBoundParameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) begin { ## Remove -RemainingArguments to stop it being passed on. [ref] $null = $PSBoundParameters.Remove('RemainingArguments'); } process { $moduleFileInfo = GetModuleCache @PSBoundParameters; return ($null -ne $moduleFileInfo); } #end process } #end function TestModuleCache function GetModuleCacheManifest { <# .SYNOPSIS Returns a zipped module's manifest. #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( ## File path to the zipped module [Parameter(Mandatory)] [System.String] $Path, [ValidateSet('PSGallery','GitHub')] [System.String] $Provider = 'PSGallery' ) begin { if (-not (Test-Path -Path $Path -PathType Leaf)) { throw ($localized.InvalidPathError -f 'Module', $Path); } } process { Write-Debug -Message 'Loading ''System.IO.Compression'' .NET binaries.'; [ref] $null = [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression"); [ref] $null = [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem"); $moduleFileInfo = Get-Item -Path $Path; if ($Provider -eq 'PSGallery') { $moduleName = $moduleFileInfo.Name -replace '\.zip', ''; } elseif ($Provider -eq 'GitHub') { ## If we have a GitHub module, trim the _Owner_Branch.zip; if we have a PSGallery module, trim the .zip $moduleName = $moduleFileInfo.Name -replace '_\S+_\S+\.zip', ''; } $moduleManifestName = '{0}.psd1' -f $moduleName; $temporaryArchivePath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$moduleName.psd1"; try { ### Open the ZipArchive with read access WriteVerbose -Message ($localized.OpeningArchive -f $moduleFileInfo.FullName); $archive = New-Object System.IO.Compression.ZipArchive(New-Object System.IO.FileStream($moduleFileInfo.FullName, [System.IO.FileMode]::Open)); ## Zip archive entries are case-sensitive, therefore, we need to search for a match and can't use ::GetEntry() foreach ($archiveEntry in $archive.Entries) { if ($archiveEntry.Name -eq $moduleManifestName) { $moduleManifestArchiveEntry = $archiveEntry; } } [System.IO.Compression.ZipFileExtensions]::ExtractToFile($moduleManifestArchiveEntry, $temporaryArchivePath, $true); $moduleManifest = ConvertToConfigurationData -ConfigurationData $temporaryArchivePath; } catch { Write-Error ($localized.ReadingArchiveItemError -f $moduleManifestName); } finally { if ($null -ne $archive) { WriteVerbose -Message ($localized.ClosingArchive -f $moduleFileInfo.FullName); $archive.Dispose(); } Remove-Item -Path $temporaryArchivePath -Force; } return $moduleManifest; } #end process } #end function GetModuleCacheVersion function RenameModuleCacheVersion { <# .SYNOPSIS Renames a cached module zip file with its version number. #> [CmdletBinding(DefaultParameterSetName = 'PSGallery')] [OutputType([System.IO.FileInfo])] param ( ## PowerShell module/DSC resource module name [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Destination directory path to download the PowerShell module/DSC resource module to [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Path, ## GitHub module repository owner [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'GitHub')] [ValidateNotNullOrEmpty()] [System.String] $Owner, ## GitHub module branch [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'GitHub')] [ValidateNotNullOrEmpty()] [System.String] $Branch ) process { if ($PSCmdlet.ParameterSetName -eq 'GitHub') { $moduleManifest = GetModuleCacheManifest -Path $Path -Provider 'GitHub'; $versionedModuleFilename = '{0}-v{1}_{2}_{3}.zip' -f $Name, $moduleManifest.ModuleVersion, $Owner, $Branch; } else { $moduleManifest = GetModuleCacheManifest -Path $Path; $versionedModuleFilename = '{0}-v{1}.zip' -f $Name, $moduleManifest.ModuleVersion; } $versionedModulePath = Join-Path -Path (Split-Path -Path $Path -Parent) -ChildPath $versionedModuleFilename; if (Test-Path -Path $versionedModulePath -PathType Leaf) { ## Remove existing version module Remove-Item -Path $versionedModulePath -Force -Confirm:$false; } Rename-Item -Path $Path -NewName $versionedModuleFilename; return (Get-Item -Path $versionedModulePath); } #end process } #end function RenameModuleCacheVersion function InvokeModuleDownloadFromPSGallery { <# .SYNOPSIS Downloads a PowerShell module/DSC resource from the PowerShell gallery to the host's module cache. #> [CmdletBinding(DefaultParameterSetName = 'LatestAvailable')] [OutputType([System.IO.FileInfo])] param ( ## PowerShell module/DSC resource module name [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Destination directory path to download the PowerShell module/DSC resource module to [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath = (GetConfigurationData -Configuration Host).ModuleCachePath, ## The minimum version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'MinimumVersion')] [ValidateNotNullOrEmpty()] [System.Version] $MinimumVersion, ## The exact version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'RequiredVersion')] [ValidateNotNullOrEmpty()] [System.Version] $RequiredVersion, ## Catch all, for splatting parameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) process { $destinationModuleName = '{0}.zip' -f $Name; $moduleCacheDestinationPath = Join-Path -Path $DestinationPath -ChildPath $destinationModuleName; $setResourceDownloadParams = @{ DestinationPath = $moduleCacheDestinationPath; Uri = ResolvePSGalleryModuleUri @PSBoundParameters; NoCheckSum = $true; } $moduleDestinationPath = SetResourceDownload @setResourceDownloadParams; return (RenameModuleCacheVersion -Name $Name -Path $moduleDestinationPath); } #end process } #end function InvokeModuleDownloadFromPSGallery function InvokeModuleDownloadFromGitHub { <# .SYNOPSIS Downloads a DSC resource if it has not already been downloaded from Github. .NOTES Uses the GitHubRepository module! #> [CmdletBinding()] [OutputType([System.IO.DirectoryInfo])] param ( ## PowerShell DSC resource module name [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Destination directory path to download the PowerShell module/DSC resource module to [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath = (GetConfigurationData -Configuration Host).ModuleCachePath, ## The GitHub repository owner, typically 'PowerShell' [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Owner, ## The GitHub repository name, normally the DSC module's name [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Repository = $Name, ## The GitHub branch to download, defaults to the 'master' branch [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Branch = 'master', ## Override the local directory name. Only used if the repository name does not ## match the DSC module name [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $OverrideRepositoryName = $Name, ## Force a download, overwriting any existing resources [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force, ## Catch all, for splatting parameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) begin { if (-not $PSBoundParameters.ContainsKey('Owner')) { throw ($localized.MissingParameterError -f 'Owner'); } if (-not $PSBoundParameters.ContainsKey('Branch')) { WriteWarning -Message ($localized.NoModuleBranchSpecified -f $Name); } ## Remove -RemainingArguments to stop it being passed on. [ref] $null = $PSBoundParameters.Remove('RemainingArguments'); ## Add Repository and Branch as they might not have been explicitly passed. $PSBoundParameters['Repository'] = $Repository; $PSBoundParameters['Branch'] = $Branch; } process { ## GitHub modules are suffixed with .Owner_Branch.zip $destinationModuleName = '{0}_{1}_{2}.zip' -f $Name, $Owner, $Branch; $moduleCacheDestinationPath = Join-Path -Path $DestinationPath -ChildPath $destinationModuleName; $setResourceDownloadParams = @{ DestinationPath = $moduleCacheDestinationPath; Uri = ResolveGitHubModuleUri @PSBoundParameters; NoCheckSum = $true; } $moduleDestinationPath = SetResourceDownload @setResourceDownloadParams; return (RenameModuleCacheVersion -Name $Name -Path $moduleDestinationPath -Owner $Owner -Branch $Branch); } #end process } #end function InvokeModuleDownloadFromGitHub function InvokeModuleCacheDownload { <# .SYNOPSIS Downloads a PowerShell module (DSC resource) into the module cache. #> [CmdletBinding(DefaultParameterSetName = 'Name')] [OutputType([System.IO.FileInfo])] param ( ## PowerShell module/DSC resource module name [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Name, ## The minimum version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [ValidateNotNullOrEmpty()] [System.Version] $MinimumVersion, ## The exact version of the module required [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.Version] $RequiredVersion, ## GitHub repository owner [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Owner, ## The GitHub repository name, normally the DSC module's name [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Repository = $Name, ## GitHub repository branch [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Branch, ## Source Filesystem module path [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateNotNullOrEmpty()] [System.String] $Path, ## Provider used to download the module [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameMinimum')] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'NameRequired')] [ValidateSet('PSGallery','GitHub','FileSystem')] [System.String] $Provider, ## Lability PowerShell module info hashtable [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Module')] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable[]] $Module, ## Destination directory path to download the PowerShell module/DSC resource module to [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath = (GetConfigurationData -Configuration Host).ModuleCachePath, ## Force a download of the module(s) even if they already exist in the cache. [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force, ## Catch all to be able to pass parameter via $PSBoundParameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) begin { ## Remove -RemainingArguments to stop it being passed on. [ref] $null = $PSBoundParameters.Remove('RemainingArguments'); if ($PSCmdlet.ParameterSetName -ne 'Module') { ## Create a module hashtable $newModule = @{ Name = $Name; Repository = $Repository; } if ($PSBoundParameters.ContainsKey('MinimumVersion')) { $newModule['MinimumVersion'] = $MinimumVersion; } if ($PSBoundParameters.ContainsKey('RequiredVersion')) { $newModule['RequiredVersion'] = $RequiredVersion; } if ($PSBoundParameters.ContainsKey('Owner')) { $newModule['Owner'] = $Owner; } if ($PSBoundParameters.ContainsKey('Branch')) { $newModule['Branch'] = $Branch; } if ($PSBoundParameters.ContainsKey('Path')) { $newModule['Path'] = $Path; } if ($PSBoundParameters.ContainsKey('Provider')) { $newModule['Provider'] = $Provider; } $Module = $newModule; } } process { foreach ($moduleInfo in $Module) { if ((-not (TestModuleCache @moduleInfo)) -or ($Force)) { if ((-not $moduleInfo.ContainsKey('Provider')) -or ($moduleInfo['Provider'] -eq 'PSGallery')) { if ($moduleInfo.ContainsKey('RequiredVersion')) { WriteVerbose -Message ($localized.ModuleVersionNotCached -f $moduleInfo.Name, $moduleInfo.RequiredVersion); } elseif ($moduleInfo.ContainsKey('MinimumVersion')) { WriteVerbose -Message ($localized.ModuleMinmumVersionNotCached -f $moduleInfo.Name, $moduleInfo.MinimumVersion); } else { WriteVerbose -Message ($localized.ModuleNotCached -f $moduleInfo.Name); } InvokeModuleDownloadFromPSGallery @moduleInfo; } elseif ($moduleInfo['Provider'] -eq 'GitHub') { WriteVerbose -Message ($localized.ModuleNotCached -f $moduleInfo.Name); InvokeModuleDownloadFromGitHub @moduleInfo; } elseif ($moduleInfo['Provider'] -eq 'FileSystem') { ## We should never get here as filesystem modules are not cached. ## If the test doesn't throw, it should return $true. } } else { GetModuleCache @moduleInfo; } } #end foreach module } #end process } #end function InvokeModuleDownload function ExpandModuleCache { <# .SYNOPSIS Extracts a cached PowerShell module to the specified destination module path. #> [CmdletBinding()] [OutputType([System.IO.DirectoryInfo])] param ( ## PowerShell module hashtable [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable[]] $Module, ## Destination directory path to download the PowerShell module/DSC resource module to [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, ## Removes existing module directory if present [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Clean, ## Catch all to be able to pass parameter via $PSBoundParameters [Parameter(ValueFromRemainingArguments)] $RemainingArguments ) begin { [ref] $null = $PSBoundParameters.Remove('RemainingArguments'); } process { foreach ($moduleInfo in $Module) { $moduleFileInfo = GetModuleCache @moduleInfo; $moduleSourcePath = $moduleFileInfo.FullName; $moduleDestinationPath = Join-Path -Path $DestinationPath -ChildPath $moduleInfo.Name; if ($Clean -and (Test-Path -Path $moduleDestinationPath -PathType Container)) { WriteVerbose -Message ($localized.CleaningModuleDirectory -f $moduleDestinationPath); Remove-Item -Path $moduleDestinationPath -Recurse -Force -Confirm:$false; } if ((-not $moduleInfo.ContainsKey('Provider')) -or ($moduleInfo.Provider -eq 'PSGallery')) { WriteVerbose -Message ($localized.ExpandingModule -f $moduleDestinationPath); $expandZipArchiveParams = @{ Path = $moduleSourcePath; DestinationPath = $moduleDestinationPath; ExcludeNuSpecFiles = $true; Force = $true; Verbose = $false; WarningAction = 'SilentlyContinue'; } [ref] $null = ExpandZipArchive @expandZipArchiveParams; } #end if PSGallery elseif (($moduleInfo.ContainsKey('Provider')) -and ($moduleInfo.Provider -eq 'GitHub')) { WriteVerbose -Message ($localized.ExpandingModule -f $moduleDestinationPath); $expandGitHubZipArchiveParams = @{ Path = $moduleSourcePath; ## GitHub modules include the module directory. Therefore, we need the parent root directory DestinationPath = Split-Path -Path $moduleDestinationPath -Parent;; Repository = $moduleInfo.Name; Force = $true; Verbose = $false; WarningAction = 'SilentlyContinue'; } if ($moduleInfo.ContainsKey('OverrideRepository')) { $expandGitHubZipArchiveParams['OverrideRepository'] = $moduleInfo.OverrideRepository; } [ref] $null = ExpandGitHubZipArchive @expandGitHubZipArchiveParams; } #end if GitHub elseif (($moduleInfo.ContainsKey('Provider')) -and ($moduleInfo.Provider -eq 'FileSystem')) { if ($null -ne $moduleFileInfo) { if ($moduleFileInfo -is [System.IO.FileInfo]) { WriteVerbose -Message ($localized.ExpandingModule -f $moduleDestinationPath); $expandZipArchiveParams = @{ Path = $moduleSourcePath; DestinationPath = $moduleDestinationPath; ExcludeNuSpecFiles = $true; Force = $true; Verbose = $false; WarningAction = 'SilentlyContinue'; } [ref] $null = ExpandZipArchive @expandZipArchiveParams; } elseif ($moduleFileInfo -is [System.IO.DirectoryInfo]) { WriteVerbose -Message ($localized.CopyingModuleDirectory -f $moduleFileInfo.Name, $moduleDestinationPath); ## If the target doesn't exist create it. We may be copying a versioned ## module, i.e. \xJea\0.2.16.6 to \xJea.. if (-not (Test-Path -Path $moduleDestinationPath -PathType Container)) { New-Item -Path $moduleDestinationPath -ItemType Directory -Force; } $copyItemParams = @{ Path = "$moduleSourcePath\*"; Destination = $moduleDestinationPath; Recurse = $true; Force = $true; Verbose = $false; } Copy-Item @copyItemParams; } } } #end if FileSystem ## Only output if we found a module during this pass if ($null -ne $moduleFileInfo) { Write-Output -InputObject (Get-Item -Path $moduleDestinationPath); } } #end foreach module } #end process } #end function ExpandModule |