Lib/Resource.ps1
function NewDirectory { <# .SYNOPSIS Creates a filesystem directory. .DESCRIPTION The New-Directory cmdlet will create the target directory if it doesn't already exist. If the target path already exists, the cmdlet does nothing. #> [CmdletBinding(DefaultParameterSetName = 'ByString', SupportsShouldProcess)] [OutputType([System.IO.DirectoryInfo])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] param ( # Target filesystem directory to create [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'ByDirectoryInfo')] [ValidateNotNullOrEmpty()] [System.IO.DirectoryInfo[]] $InputObject, # Target filesystem directory to create [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'ByString')] [Alias('PSPath')] [ValidateNotNullOrEmpty()] [System.String[]] $Path ) process { Write-Debug -Message ("Using parameter set '{0}'." -f $PSCmdlet.ParameterSetName); switch ($PSCmdlet.ParameterSetName) { 'ByString' { foreach ($directory in $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)) { Write-Debug -Message ("Testing target directory '{0}'." -f $directory); if (!(Test-Path -Path $directory -PathType Container)) { if ($PSCmdlet.ShouldProcess($directory, "Create directory")) { WriteVerbose ($localized.CreatingDirectory -f $directory); New-Item -Path $directory -ItemType Directory; } } else { WriteVerbose ($localized.DirectoryExists -f $directory); Get-Item -Path $directory; } } #end foreach directory } #end byString 'ByDirectoryInfo' { foreach ($directoryInfo in $InputObject) { Write-Debug -Message ("Testing target directory '{0}'." -f $directoryInfo.FullName); if (!($directoryInfo.Exists)) { if ($PSCmdlet.ShouldProcess($directoryInfo.FullName, "Create directory")) { WriteVerbose ($localized.CreatingDirectory -f $directoryInfo.FullName); New-Item -Path $directoryInfo.FullName -ItemType Directory; } } else { WriteVerbose ($localized.DirectoryExists -f $directoryInfo.FullName); $directoryInfo; } } #end foreach directoryInfo } #end byDirectoryInfo } #end switch } #end process } #end function NewDirectory function SetResourceChecksum { <# .SYNOPSIS Creates a resource's checksum file. #> param ( ## Path of file to create the checksum of [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Path ) process { $checksumPath = '{0}.checksum' -f $Path; ## As it can take a long time to calculate the checksum, write it out to disk for future reference WriteVerbose ($localized.CalculatingResourceChecksum -f $checksumPath); $fileHash = Get-FileHash -Path $Path -Algorithm MD5 -ErrorAction Stop | Select-Object -ExpandProperty Hash; WriteVerbose ($localized.WritingResourceChecksum -f $fileHash, $checksumPath); $fileHash | Set-Content -Path $checksumPath -Force; } } #end function SetResourceChecksum function GetResourceDownload { <# .SYNOPSIS Retrieves a downloaded resource's checksum. .NOTES Based upon https://github.com/iainbrighton/cRemoteFile/blob/master/DSCResources/VE_RemoteFile/VE_RemoteFile.ps1 #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Checksum, [Parameter(ValueFromPipelineByPropertyName)] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent ) process { $checksumPath = '{0}.checksum' -f $DestinationPath; if (-not (Test-Path -Path $DestinationPath)) { WriteVerbose ($localized.MissingResourceFile -f $DestinationPath); } elseif (-not (Test-Path -Path $checksumPath)) { [ref] $null = SetResourceChecksum -Path $DestinationPath; } if (Test-Path -Path $checksumPath) { Write-Debug -Message ('MD5 checksum file ''{0}'' found.' -f $checksumPath); $md5Checksum = (Get-Content -Path $checksumPath -Raw).Trim(); Write-Debug -Message ('Discovered MD5 checksum ''{0}''.' -f $md5Checksum); } else { Write-Debug -Message ('MD5 checksum file ''{0}'' not found.' -f $checksumPath); } $resource = @{ DestinationPath = $DestinationPath; Uri = $Uri; Checksum = $md5Checksum; } return $resource; } #end process } #end function GetResourceDownload function TestResourceDownload { <# .SYNOPSIS Tests if a web resource has been downloaded and whether the MD5 checksum is correct. .NOTES Based upon https://github.com/iainbrighton/cRemoteFile/blob/master/DSCResources/VE_RemoteFile/VE_RemoteFile.ps1 #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Checksum, [Parameter(ValueFromPipelineByPropertyName)] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent ) process { $resource = GetResourceDownload @PSBoundParameters; if ([System.String]::IsNullOrEmpty($Checksum) -and (Test-Path -Path $DestinationPath -PathType Leaf)) { WriteVerbose ($localized.ResourceChecksumNotSpecified -f $DestinationPath); return $true; } elseif ($Checksum -eq $resource.Checksum) { WriteVerbose ($localized.ResourceChecksumMatch -f $DestinationPath, $Checksum); return $true; } else { WriteVerbose ($localized.ResourceChecksumMismatch -f $DestinationPath, $Checksum); return $false; } } #end process } #end function TestResourceDownload function SetResourceDownload { <# .SYNOPSIS Downloads a (web) resource and creates a MD5 checksum. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Checksum, [Parameter(ValueFromPipelineByPropertyName)] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent ) begin { $parentDestinationPath = Split-Path -Path $DestinationPath -Parent; [ref] $null = NewDirectory -Path $parentDestinationPath; } process { if (-not $PSBoundParameters.ContainsKey('BufferSize')) { $systemUri = New-Object -TypeName System.Uri -ArgumentList @($uri); if ($systemUri.IsFile) { $BufferSize = 1MB; } } WriteVerbose ($localized.DownloadingResource -f $Uri, $DestinationPath); InvokeWebClientDownload -DestinationPath $DestinationPath -Uri $Uri -BufferSize $BufferSize; ## Create the checksum file for future reference [ref] $null = SetResourceChecksum -Path $DestinationPath; } #end process } #end function SetResourceDownload function InvokeWebClientDownload { <# .SYNOPSIS Downloads a (web) resource using System.Net.WebClient. .NOTES This solves issue #19 when running downloading resources using BITS under alternative credentials. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.String] $DestinationPath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $Uri, [Parameter(ValueFromPipelineByPropertyName)] [System.UInt32] $BufferSize = 64KB, [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential ) process { try { [System.Net.WebClient] $webClient = New-Object -TypeName 'System.Net.WebClient'; $webClient.Headers.Add('user-agent', $labDefaults.ModuleName); $webClient.Proxy = [System.Net.WebRequest]::GetSystemWebProxy(); if (-not $webClient.Proxy.IsBypassed($Uri)) { $proxyInfo = $webClient.Proxy.GetProxy($Uri); WriteVerbose ($localized.UsingProxyServer -f $proxyInfo.AbsoluteUri); } if ($Credential) { $webClient.Credentials = $Credential; $webClient.Proxy.Credentials = $Credential; } else { $webClient.UseDefaultCredentials = $true; $webClient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials; } [System.IO.Stream] $inputStream = $webClient.OpenRead($Uri); [System.UInt64] $contentLength = $webClient.ResponseHeaders['Content-Length']; $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath); [System.IO.Stream] $outputStream = [System.IO.File]::Create($path); [System.Byte[]] $buffer = New-Object -TypeName System.Byte[] -ArgumentList $BufferSize; [System.UInt64] $bytesRead = 0; [System.UInt64] $totalBytes = 0; $writeProgessActivity = $localized.DownloadingActivity -f $Uri; do { $bytesRead = $inputStream.Read($buffer, 0, $buffer.Length); $totalBytes += $bytesRead; $outputStream.Write($buffer, 0, $bytesRead); ## Avoid divide by zero if ($contentLength -gt 0) { [System.Byte] $percentComplete = ($totalBytes/$contentLength) * 100; $writeProgressParams = @{ Activity = $writeProgessActivity; PercentComplete = $percentComplete; Status = $localized.DownloadStatus -f $totalBytes, $contentLength, $percentComplete; } Write-Progress @writeProgressParams; } } while ($bytesRead -ne 0) $outputStream.Close(); return (Get-Item -Path $path); } catch { throw ($localized.ResourceDownloadFailedError -f $Uri); } finally { Write-Progress -Activity $writeProgessActivity -Completed; if ($null -ne $outputStream) { $outputStream.Close(); } if ($null -ne $inputStream) { $inputStream.Close(); } if ($null -ne $webClient) { $webClient.Dispose(); } } } #end process } #end function InvokeWebClientDownload function InvokeResourceDownload { <# .SYNOPSIS Downloads a web resource if it has not already been downloaded or the checksum is incorrect. #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.String] $Checksum, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force, [Parameter(ValueFromPipelineByPropertyName)] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent ) process { [ref] $null = $PSBoundParameters.Remove('Force'); if (-not (TestResourceDownload @PSBoundParameters) -or $Force) { SetResourceDownload @PSBoundParameters -Verbose:$false; } $resource = GetResourceDownload @PSBoundParameters; return [PSCustomObject] $resource; } #end process } #end function InvokeResourceDownload |