SharedResources/Src/InstallPrimaryHeadNode/xPSDesiredStateConfiguration/DSCResources/MSFT_xRemoteFile/MSFT_xRemoteFile.psm1 (506 lines of code) (raw):
$moduleRoot = Split-Path `
-Path $MyInvocation.MyCommand.Path `
-Parent
#region LocalizedData
$Culture = 'en-us'
if (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath $PSUICulture))
{
$Culture = $PSUICulture
}
Import-LocalizedData `
-BindingVariable LocalizedData `
-Filename MSFT_xRemoteFile.psd1 `
-BaseDirectory $moduleRoot `
-UICulture $Culture
#endregion
# Path where cache will be stored. It's cleared whenever LCM gets new configuration.
$script:cacheLocation = "$env:ProgramData\Microsoft\Windows\PowerShell\Configuration\BuiltinProvCache\MSFT_xRemoteFile"
<#
.Synopsis
The Get-TargetResource function is used to fetch the status of file specified in DestinationPath on the target machine.
#>
function Get-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Uri
)
# Check whether DestinationPath is existing file
$ensure = "Absent"
$pathItemType = Get-PathItemType -path $DestinationPath
switch($pathItemType)
{
"File"
{
Write-Verbose -Message $($LocalizedData.DestinationPathIsExistingFile `
-f ${DestinationPath})
$ensure = "Present"
}
"Directory"
{
Write-Verbose -Message $($LocalizedData.DestinationPathIsExistingPath `
-f ${DestinationPath})
# If it's existing directory, let's check whether expectedDestinationPath exists
$uriFileName = Split-Path $Uri -Leaf
$expectedDestinationPath = Join-Path $DestinationPath $uriFileName
if (Test-Path $expectedDestinationPath)
{
Write-Verbose -Message $($LocalizedData.FileExistsInDestinationPath `
-f ${uriFileName})
$ensure = "Present"
}
}
"Other"
{
Write-Verbose -Message $($LocalizedData.DestinationPathUnknownType `
-f ${DestinationPath},${pathItemType})
}
"NotExists"
{
Write-Verbose -Message $($LocalizedData.DestinationPathDoesNotExist `
-f ${DestinationPath})
}
}
$returnValue = @{
DestinationPath = $DestinationPath
Uri = $Uri
Ensure = $ensure
}
$returnValue
}
<#
.Synopsis
The Set-TargetResource function is used to download file found under Uri location to DestinationPath
Additional parameters can be specified to configure web request
#>
function Set-TargetResource
{
[CmdletBinding()]
param
(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Uri,
[System.String]
$UserAgent,
[Microsoft.Management.Infrastructure.CimInstance[]]
$Headers,
[System.Management.Automation.PSCredential]
$Credential,
[parameter(Mandatory = $false)]
[System.Boolean]
$MatchSource = $true,
[Uint32]
$TimeoutSec,
[System.String]
$Proxy,
[System.Management.Automation.PSCredential]
$ProxyCredential
)
# Validate Uri
if (-not (Test-UriScheme -uri $Uri -scheme "http|https|file"))
{
$errorMessage = $($LocalizedData.InvalidWebUriError) `
-f ${Uri}
New-InvalidDataException `
-errorId "UriValidationFailure" `
-errorMessage $errorMessage
}
# Validate DestinationPath scheme
if (-not (Test-UriScheme -uri $DestinationPath -scheme "file"))
{
$errorMessage = $($LocalizedData.InvalidDestinationPathSchemeError `
-f ${DestinationPath})
New-InvalidDataException `
-errorId "DestinationPathSchemeValidationFailure" `
-errorMessage $errorMessage
}
# Validate DestinationPath is not UNC path
if ($DestinationPath.StartsWith("\\"))
{
$errorMessage = $($LocalizedData.DestinationPathIsUncError `
-f ${DestinationPath})
New-InvalidDataException `
-errorId "DestinationPathIsUncFailure" `
-errorMessage $errorMessage
}
# Validate DestinationPath does not contain invalid characters
@('*','?','"','<','>','|') | % {
if ($DestinationPath.Contains($_) ){
$errorMessage = $($LocalizedData.DestinationPathHasInvalidCharactersError `
-f ${DestinationPath})
New-InvalidDataException `
-errorId "DestinationPathHasInvalidCharactersError" `
-errorMessage $errorMessage
}
}
# Validate DestinationPath does not end with / or \ (Invoke-WebRequest requirement)
if ($DestinationPath.EndsWith('/') -or $DestinationPath.EndsWith('\')){
$errorMessage = $($LocalizedData.DestinationPathEndsWithInvalidCharacterError `
-f ${DestinationPath})
New-InvalidDataException `
-errorId "DestinationPathEndsWithInvalidCharacterError" `
-errorMessage $errorMessage
}
# Check whether DestinationPath's parent directory exists. Create if it doesn't.
$destinationPathParent = Split-Path $DestinationPath -Parent
if (-not (Test-Path $destinationPathParent))
{
$null = New-Item -ItemType Directory -Path $destinationPathParent -Force
}
# Check whether DestinationPath's leaf is an existing folder
$uriFileName = Split-Path $Uri -Leaf
if (Test-Path $DestinationPath -PathType Container)
{
$DestinationPath = Join-Path $DestinationPath $uriFileName
}
# Remove DestinationPath and MatchSource from parameters as they are not parameters of Invoke-WebRequest
$null = $PSBoundParameters.Remove("DestinationPath")
$null = $PSBoundParameters.Remove("MatchSource")
# Convert headers to hashtable
$null = $PSBoundParameters.Remove("Headers")
$headersHashtable = $null
if ($Headers -ne $null)
{
$headersHashtable = Convert-KeyValuePairArrayToHashtable -array $Headers
}
# Invoke web request
try
{
Write-Verbose -Message $($LocalizedData.DownloadingURI `
-f ${DestinationPath},${URI})
Invoke-WebRequest @PSBoundParameters -Headers $headersHashtable -outFile $DestinationPath
}
catch [System.OutOfMemoryException]
{
$errorMessage = $($LocalizedData.DownloadOutOfMemoryException `
-f $_)
New-InvalidDataException `
-errorId "SystemOutOfMemoryException" `
-errorMessage $errorMessage
}
catch [System.Exception]
{
$errorMessage = $($LocalizedData.DownloadException `
-f $_)
New-InvalidDataException `
-errorId "SystemException" `
-errorMessage $errorMessage
}
# Update cache
if (Test-Path -Path $DestinationPath)
{
$downloadedFile = Get-Item -Path $DestinationPath
$lastWriteTime = $downloadedFile.LastWriteTimeUtc
$filesize = $downloadedFile.Length
$inputObject = @{}
$inputObject["LastWriteTime"] = $lastWriteTime
$inputObject["FileSize"] = $filesize
Update-Cache -DestinationPath $DestinationPath -Uri $Uri -InputObject $inputObject
}
}
<#
.Synopsis
The Test-TargetResource function is used to validate if the DestinationPath exists on the machine.
#>
function Test-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Uri,
[System.String]
$UserAgent,
[Microsoft.Management.Infrastructure.CimInstance[]]
$Headers,
[System.Management.Automation.PSCredential]
$Credential,
[parameter(Mandatory = $false)]
[System.Boolean]
$MatchSource = $true,
[Uint32]
$TimeoutSec,
[System.String]
$Proxy,
[System.Management.Automation.PSCredential]
$ProxyCredential
)
# Check whether DestinationPath points to existing file or directory
$fileExists = $false
$uriFileName = Split-Path $Uri -Leaf
$pathItemType = Get-PathItemType -Path $DestinationPath
switch($pathItemType)
{
"File"
{
Write-Verbose -Message $($LocalizedData.DestinationPathIsExistingFile `
-f ${DestinationPath})
if ($MatchSource) {
$file = Get-Item -Path $DestinationPath
# Getting cache. It's cleared every time user runs Start-DscConfiguration
$cache = Get-Cache -DestinationPath $DestinationPath -Uri $Uri
if ($cache -ne $null `
-and ($cache.LastWriteTime -eq $file.LastWriteTimeUtc) `
-and ($cache.FileSize -eq $file.Length))
{
Write-Verbose -Message $($LocalizedData.CacheReflectsCurrentState)
$fileExists = $true
}
else
{
Write-Verbose -Message $($LocalizedData.CacheIsEmptyOrNotMatchCurrentState)
}
}
else
{
Write-Verbose -Message $($LocalizedData.MatchSourceFalse)
$fileExists = $true
}
}
"Directory"
{
Write-Verbose -Message $($LocalizedData.DestinationPathIsExistingPath `
-f ${DestinationPath})
$expectedDestinationPath = Join-Path -Path $DestinationPath -ChildPath $uriFileName
if (Test-Path -Path $expectedDestinationPath)
{
if ($MatchSource)
{
$file = Get-Item -Path $expectedDestinationPath
$cache = Get-Cache -DestinationPath $expectedDestinationPath -Uri $Uri
if ($cache -ne $null -and ($cache.LastWriteTime -eq $file.LastWriteTimeUtc))
{
Write-Verbose -Message $($LocalizedData.CacheReflectsCurrentState)
$fileExists = $true
}
else
{
Write-Verbose -Message $($LocalizedData.CacheIsEmptyOrNotMatchCurrentState)
}
}
else
{
Write-Verbose -Message $($LocalizedData.MatchSourceFalse)
$fileExists = $true
}
}
}
"Other"
{
Write-Verbose -Message $($LocalizedData.DestinationPathUnknownType `
-f ${DestinationPath},${pathItemType})
}
"NotExists"
{
Write-Verbose -Message $($LocalizedData.DestinationPathDoesNotExist `
-f ${DestinationPath})
}
}
$result = $fileExists
$result
}
<#
.Synopsis
Throws terminating error of category InvalidData with specified errorId and errorMessage
#>
function New-InvalidDataException
{
param(
[parameter(Mandatory = $true)]
[System.String]
$errorId,
[parameter(Mandatory = $true)]
[System.String]
$errorMessage
)
$errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData
$exception = New-Object `
-TypeName System.InvalidOperationException `
-ArgumentList $errorMessage
$errorRecord = New-Object `
-TypeName System.Management.Automation.ErrorRecord `
-ArgumentList $exception, $errorId, $errorCategory, $null
throw $errorRecord
}
<#
.Synopsis
Checks whether given URI represents specific scheme
.Description
Most common schemes: file, http, https, ftp
We can also specify logical expressions like: [http|https]
#>
function Test-UriScheme
{
param (
[parameter(Mandatory = $true)]
[System.String]
$uri,
[parameter(Mandatory = $true)]
[System.String]
$scheme
)
$newUri = $uri -as [System.URI]
$newUri.AbsoluteURI -ne $null -and $newUri.Scheme -match $scheme
}
<#
.Synopsis
Gets type of the item which path points to.
.Outputs
File, Directory, Other or NotExists
#>
function Get-PathItemType
{
param (
[parameter(Mandatory = $true)]
[System.String]
$path
)
$type = $null
# Check whether path exists
if (Test-Path $path)
{
# Check type of the path
$pathItem = Get-Item -Path $path
$pathItemType = $pathItem.GetType().Name
if ($pathItemType -eq "FileInfo")
{
$type = "File"
}
elseif ($pathItemType -eq "DirectoryInfo")
{
$type = "Directory"
}
else
{
$type = "Other"
}
}
else
{
$type = "NotExists"
}
return $type
}
<#
.Synopsis
Converts CimInstance array of type KeyValuePair to hashtable
#>
function Convert-KeyValuePairArrayToHashtable
{
param (
[parameter(Mandatory = $true)]
[Microsoft.Management.Infrastructure.CimInstance[]]
$array
)
$hashtable = @{}
foreach($item in $array)
{
$hashtable += @{$item.Key = $item.Value}
}
return $hashtable
}
<#
.Synopsis
Gets cache for specific DestinationPath and Uri
#>
function Get-Cache
{
param (
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Uri
)
$cacheContent = $null
$key = Get-CacheKey -DestinationPath $DestinationPath -Uri $Uri
$path = Join-Path -Path $script:cacheLocation -ChildPath $key
Write-Verbose -Message $($LocalizedData.CacheLookingForPath `
-f ${Path})
if(-not (Test-Path -Path $path))
{
Write-Verbose -Message $($LocalizedData.CacheNotFoundForPath `
-f ${DestinationPath},${Uri},${Key})
$cacheContent = $null
}
else
{
$cacheContent = Import-CliXml -Path $path
Write-Verbose -Message $($LocalizedData.CacheFoundForPath `
-f ${DestinationPath},${Uri},${Key})
}
return $cacheContent
}
<#
.Synopsis
Creates or updates cache for specific DestinationPath and Uri
#>
function Update-Cache
{
param (
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Uri,
[parameter(Mandatory = $true)]
[Object]
$InputObject
)
$key = Get-CacheKey -DestinationPath $DestinationPath -Uri $Uri
$path = Join-Path -Path $script:cacheLocation -ChildPath $key
if(-not (Test-Path -Path $script:cacheLocation))
{
$null = New-Item -ItemType Directory -Path $script:cacheLocation
}
Write-Verbose -Message $($LocalizedData.UpdatingCache `
-f ${DestinationPath},${Uri},${Key})
Export-CliXml -Path $path -InputObject $InputObject -Force
}
<#
.Synopsis
Returns cache key for given parameters
#>
function Get-CacheKey
{
param (
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Uri
)
return [string]::Join("", @($DestinationPath, $Uri)).GetHashCode().ToString()
}
Export-ModuleMember -Function *-TargetResource