SharedResources/Src/InstallPrimaryHeadNode/xPSDesiredStateConfiguration/DSCResources/xFileUpload/xFileUpload.schema.psm1 (472 lines of code) (raw):
Configuration xFileUpload
{
<#
.SYNOPSIS
Configuration uploads file or folder to the smb share
.DESCRIPTION
.EXAMPLE
xFileUpload -destinationPath "\\machine\share" -sourcePath "C:\folder\file" -username "domain\user" -password "password"
.PARAMETER destinationPath
Upload destination (has to point to a share or it's existing subfolder) e.g. \\machinename\sharename\destinationfolder
.PARAMETER sourcePath
Upload source e.g. C:\folder\file.txt
.PARAMETER credential
Credentials to access share where file/folder should be uploaded
.PARAMETER certificateThumbprint
Thumbprint of the certificate which should be used for encryption/decryption
.NOTES
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
param (
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]
$destinationPath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]
$sourcePath,
[PSCredential]
$credential,
[String]
$certificateThumbprint
)
$cacheLocation = "$env:ProgramData\Microsoft\Windows\PowerShell\Configuration\BuiltinProvCache\MSFT_xFileUpload"
if ($credential)
{
$username = $credential.UserName
# Encrypt password
$password = Invoke-Command -ScriptBlock ([ScriptBlock]::Create($getEncryptedPassword)) -ArgumentList $credential, $certificateThumbprint
}
Script FileUpload
{
# Get script is not implemented cause reusing Script resource's schema does not make sense
GetScript = {
$returnValue = @{
}
$returnValue
};
SetScript = {
# Generating credential object if password and username are specified
$credential = $null
if (($using:password) -and ($using:username))
{
# Validate that certificate thumbprint is specified
if(-not $using:certificateThumbprint)
{
$errorMessage = "Certificate thumbprint has to be specified if credentials are present."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "CertificateThumbprintIsRequired", $errorMessage, "InvalidData"
}
Write-Debug "Username and password specified."
# Decrypt password
$decryptedPassword = Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:getDecryptedPassword)) -ArgumentList $using:password, $using:certificateThumbprint
# Generate credential
$securePassword = ConvertTo-SecureString $decryptedPassword -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($using:username, $securePassword)
}
# Validate DestinationPath is UNC path
if (!($using:destinationPath -as [System.Uri]).isUnc)
{
$errorMessage = "Destination path $using:destinationPath is not a valid UNC path."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathIsNotUNCFailure", $errorMessage, "InvalidData"
}
# Verify source is localpath
if (!(($using:sourcePath -as [System.Uri]).Scheme -match "file"))
{
$errorMessage = "Source path $using:sourcePath has to be local path."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "SourcePathIsNotLocalFailure", $errorMessage, "InvalidData"
}
# Check whether source path is existing file or directory
$sourcePathType = $null
if (!(Test-Path $using:sourcePath))
{
$errorMessage = "Source path $using:sourcePath does not exist."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "SourcePathDoesNotExistFailure", $errorMessage, "InvalidData"
}
else
{
$item = Get-Item $using:sourcePath
switch ($item.GetType().Name)
{
"FileInfo" {
$sourcePathType = "File"
}
"DirectoryInfo" {
$sourcePathType = "Directory"
}
}
}
Write-Debug "SourcePath $using:sourcePath is of type: $sourcePathType"
$psDrive = $null
# Mount the drive only if credentials are specified and it's currently not accessible
if ($credential)
{
if (Test-Path $using:destinationPath -ErrorAction Ignore)
{
Write-Debug "Destination path $using:destinationPath is already accessible. No mount needed."
}
else
{
$psDriveArgs = @{ Name = ([guid]::NewGuid()); PSProvider = "FileSystem"; Root = $using:destinationPath; Scope = "Private"; Credential = $credential }
try
{
Write-Debug "Create psdrive with destination path $using:destinationPath..."
$psDrive = New-PSDrive @psDriveArgs -ErrorAction Stop
}
catch
{
$errorMessage = "Cannot access destination path $using:destinationPath with given Credential"
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathNotAccessibleFailure", $errorMessage, "InvalidData"
}
}
}
try
{
# Get expected destination path
$expectedDestinationPath = $null
if (!(Test-Path $using:destinationPath))
{
# DestinationPath has to exist
$errorMessage = "Invalid parameter values: DestinationPath doesn't exist, but has to be existing directory."
Throw-TerminatingError -errorMessage $errorMessage -errorCategory "InvalidData" -errorId "DestinationPathDoesNotExistFailure"
}
else
{
$item = Get-Item $using:destinationPath
switch ($item.GetType().Name)
{
"FileInfo" {
# DestinationPath cannot be file
$errorMessage = "Invalid parameter values: DestinationPath is file, but has to be existing directory."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathCannotBeFileFailure", $errorMessage, "InvalidData"
}
"DirectoryInfo" {
$expectedDestinationPath = Join-Path $using:destinationPath (Split-Path $using:sourcePath -Leaf)
}
}
Write-Debug "ExpectedDestinationPath is $expectedDestinationPath"
}
# Copy destination path
try
{
Write-Debug "Copying $using:sourcePath to $using:destinationPath"
Copy-Item -path $using:sourcePath -Destination $using:destinationPath -Recurse -Force -ErrorAction Stop
}
catch
{
$errorMessage = "Couldn't copy source path $using:sourcePath to $using:destinationPath : $($_.Exception)"
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "CopyDirectoryOverFileFailure", $errorMessage, "InvalidData"
}
# Verify whether expectedDestinationPath was created
if (!(Test-Path $expectedDestinationPath))
{
$errorMessage = "Destination path $using:destinationPath could not be created"
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathNotCreatedFailure", $errorMessage, "InvalidData"
}
# If expectedDestinationPath exists
else
{
Write-Verbose "$sourcePathType $expectedDestinationPath has been successfully created"
# Update cache
$uploadedItem = Get-Item $expectedDestinationPath
$lastWriteTime = $uploadedItem.LastWriteTimeUtc
$inputObject = @{}
$inputObject["LastWriteTimeUtc"] = $lastWriteTime
$key = [string]::Join("", @($using:destinationPath, $using:sourcePath, $expectedDestinationPath)).GetHashCode().ToString()
$path = Join-Path $using:cacheLocation $key
if(-not (Test-Path $using:cacheLocation))
{
mkdir $using:cacheLocation | Out-Null
}
Write-Debug "Updating cache for DestinationPath = $using:destinationPath and SourcePath = $using:sourcePath. CacheKey = $key"
Export-CliXml -Path $path -InputObject $inputObject -Force
}
}
finally
{
# Remove PSDrive
if($psDrive)
{
Write-Debug "Removing PSDrive on root $($psDrive.Root)"
Remove-PSDrive $psDrive -Force
}
}
};
TestScript = {
# Generating credential object if password and username are specified
$credential = $null
if (($using:password) -and ($using:username))
{
# Validate that certificate thumbprint is specified
if(-not $using:certificateThumbprint)
{
$errorMessage = "Certificate thumbprint has to be specified if credentials are present."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "CertificateThumbprintIsRequired", $errorMessage, "InvalidData"
}
Write-Debug "Username and password specified. Generating credential"
# Decrypt password
$decryptedPassword = Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:getDecryptedPassword)) -ArgumentList $using:password, $using:certificateThumbprint
# Generate credential
$securePassword = ConvertTo-SecureString $decryptedPassword -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($using:username, $securePassword)
}
else
{
Write-Debug "No credentials specified"
}
# Validate DestinationPath is UNC path
if (!($using:destinationPath -as [System.Uri]).isUnc)
{
$errorMessage = "Destination path $using:destinationPath is not a valid UNC path."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathIsNotUNCFailure", $errorMessage, "InvalidData"
}
# Check whether source path is existing file or directory (needed for expectedDestinationPath)
$sourcePathType = $null
if (!(Test-Path $using:sourcePath))
{
$errorMessage = "Source path $using:sourcePath does not exist."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "SourcePathDoesNotExistFailure", $errorMessage, "InvalidData"
}
else
{
$item = Get-Item $using:sourcePath
switch ($item.GetType().Name)
{
"FileInfo" {
$sourcePathType = "File"
}
"DirectoryInfo" {
$sourcePathType = "Directory"
}
}
}
Write-Debug "SourcePath $using:sourcePath is of type: $sourcePathType"
$psDrive = $null
# Mount the drive only if credentials are specified and it's currently not accessible
if ($credential)
{
if (Test-Path $using:destinationPath -ErrorAction Ignore)
{
Write-Debug "Destination path $using:destinationPath is already accessible. No mount needed."
}
else
{
$psDriveArgs = @{ Name = ([guid]::NewGuid()); PSProvider = "FileSystem"; Root = $using:destinationPath; Scope = "Private"; Credential = $credential }
try
{
Write-Debug "Create psdrive with destination path $using:destinationPath..."
$psDrive = New-PSDrive @psDriveArgs -ErrorAction Stop
}
catch
{
$errorMessage = "Cannot access destination path $using:destinationPath with given Credential"
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathNotAccessibleFailure", $errorMessage, "InvalidData"
}
}
}
try
{
# Get expected destination path
$expectedDestinationPath = $null
if (!(Test-Path $using:destinationPath))
{
# DestinationPath has to exist
$errorMessage = "Invalid parameter values: DestinationPath doesn't exist or is not accessible. DestinationPath has to be existing directory."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathDoesNotExistFailure", $errorMessage, "InvalidData"
}
else
{
$item = Get-Item $using:destinationPath
switch ($item.GetType().Name)
{
"FileInfo" {
# DestinationPath cannot be file
$errorMessage = "Invalid parameter values: DestinationPath is file, but has to be existing directory."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($using:throwTerminatingError)) -ArgumentList "DestinationPathCannotBeFileFailure", $errorMessage, "InvalidData"
}
"DirectoryInfo" {
$expectedDestinationPath = Join-Path $using:destinationPath (Split-Path $using:sourcePath -Leaf)
}
}
Write-Debug "ExpectedDestinationPath is $expectedDestinationPath"
}
# Check whether ExpectedDestinationPath exists and has expected type
$itemExists = $false
if (!(Test-Path $expectedDestinationPath))
{
Write-Debug "Expected destination path doesn't exist or is not accessible"
}
# If expectedDestinationPath exists
else
{
$expectedItem = Get-Item $expectedDestinationPath
$expectedItemType = $expectedItem.GetType().Name
# If expectedDestinationPath has same type as sourcePathType, we need to verify cache to determine whether no upload is needed
if ((($expectedItemType -eq "FileInfo") -and ($sourcePathType -eq "File")) -or (($expectedItemType -eq "DirectoryInfo") -and ($sourcePathType -eq "Directory")))
{
# Get cache
Write-Debug "Getting cache for $expectedDestinationPath"
$cacheContent = $null
$key = [string]::Join("", @($using:destinationPath, $using:sourcePath, $expectedDestinationPath)).GetHashCode().ToString()
$path = Join-Path $using:cacheLocation $key
Write-Debug "Looking for cache under $path"
if (!(Test-Path $path))
{
Write-Debug "No cache found for DestinationPath = $using:destinationPath and SourcePath = $using:sourcePath. CacheKey = $key"
}
else
{
$cacheContent = Import-CliXml $path
Write-Debug "Found cache for DestinationPath = $using:destinationPath and SourcePath = $using:sourcePath. CacheKey = $key"
}
# Verify whether cache reflects current state or upload is needed
if ($cacheContent -ne $null -and ($cacheContent.LastWriteTimeUtc -eq $expectedItem.LastWriteTimeUtc))
{
# No upload needed
Write-Debug "Cache reflects current state. No need for upload."
$itemExists = $true
}
else
{
Write-Debug "Cache is empty or it doesn't reflect current state. Upload will be performed."
}
}
else
{
Write-Debug "Expected destination path: $expectedDestinationPath is of type $expectedItemType, although source path is $sourcePathType"
}
}
}
finally
{
# Remove PSDrive
if($psDrive)
{
Write-Debug "Removing PSDrive on root $($psDrive.Root)"
Remove-PSDrive $psDrive -Force
}
}
return $itemExists
};
}
}
# Encrypts password using the defined public key
$getEncryptedPassword = @'
param (
[Parameter(Mandatory = $true)]
[PSCredential] $credential,
[Parameter(Mandatory = $true)]
[String] $certificateThumbprint
)
$value = $credential.GetNetworkCredential().Password
$cert = Invoke-Command -ScriptBlock ([ScriptBlock]::Create($getCertificate)) -ArgumentList $certificateThumbprint
$encryptedPassword = $null
if($cert)
{
# Cast the public key correctly
$rsaProvider = [System.Security.Cryptography.RSACryptoServiceProvider]$cert.PublicKey.Key
if($rsaProvider -eq $null)
{
$errorMessage = "Could not get public key from certificate with thumbprint: $certificateThumbprint . Please verify certificate is valid for encryption."
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($throwTerminatingError)) -ArgumentList "DecryptionCertificateNotFound", $errorMessage, "InvalidOperation"
}
# Convert to a byte array
$keybytes = [System.Text.Encoding]::UNICODE.GetBytes($value)
# Add a null terminator to the byte array
$keybytes += 0
$keybytes += 0
# Encrypt using the public key
$encbytes = $rsaProvider.Encrypt($keybytes, $false)
# Return a string
$encryptedPassword = [Convert]::ToBase64String($encbytes)
}
else
{
$errorMessage = "Could not find certificate which matches thumbprint: $certificateThumbprint . Could not encrypt password"
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($throwTerminatingError)) -ArgumentList "EncryptionCertificateNot", $errorMessage, "InvalidOperation"
}
return $encryptedPassword
'@
# Retrieves certificate by thumbprint
$getCertificate = @'
param(
[Parameter(Mandatory = $true)]
[string] $certificateThumbprint
)
$cert = $null
foreach($certIndex in Get-Childitem cert:\LocalMachine\My)
{
if($certIndex.Thumbprint -match $certificateThumbprint)
{
$cert = $certIndex
break
}
}
if(-not $cert)
{
$errorMessage = "Error Reading certificate store for {0}. Please verify thumbprint is correct and certificate belongs to cert:\LocalMachine\My store." -f ${certificateThumbprint};
Invoke-Command -ScriptBlock ([ScriptBlock]::Create($throwTerminatingError)) -ArgumentList "InvalidPathSpecified", $errorMessage, "InvalidOperation"
}
else
{
$cert
}
'@
# Throws terminating error specified errorCategory, errorId and errorMessage
$throwTerminatingError = @'
param(
[parameter(Mandatory = $true)]
[System.String]
$errorId,
[parameter(Mandatory = $true)]
[System.String]
$errorMessage,
[parameter(Mandatory = $true)]
$errorCategory
)
$exception = New-Object System.InvalidOperationException $errorMessage
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
throw $errorRecord
'@
# Decrypts password using the defined private key
$getDecryptedPassword = @'
param (
[Parameter(Mandatory = $true)]
[String] $value,
[Parameter(Mandatory = $true)]
[String] $certificateThumbprint
)
$cert = $null
foreach($certIndex in Get-Childitem cert:\LocalMachine\My)
{
if($certIndex.Thumbprint -match $certificateThumbprint)
{
$cert = $certIndex
break
}
}
if(-not $cert)
{
$errorMessage = "Error Reading certificate store for {0}. Please verify thumbprint is correct and certificate belongs to cert:\LocalMachine\My store." -f ${certificateThumbprint};
$exception = New-Object System.InvalidOperationException $errorMessage
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "InvalidPathSpecified", "InvalidOperation", $null
throw $errorRecord
}
$decryptedPassword = $null
# Get RSA provider
$rsaProvider = [System.Security.Cryptography.RSACryptoServiceProvider]$cert.PrivateKey
if($rsaProvider -eq $null)
{
$errorMessage = "Could not get private key from certificate with thumbprint: $certificateThumbprint . Please verify certificate is valid for decryption."
$exception = New-Object System.InvalidOperationException $errorMessage
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "DecryptionCertificateNotFound", "InvalidOperation", $null
throw $errorRecord
}
# Convert to bytes array
$encBytes = [Convert]::FromBase64String($value)
# Decrypt bytes
$decryptedBytes = $rsaProvider.Decrypt($encBytes, $false)
# Convert to string
$decryptedPassword = [System.Text.Encoding]::Unicode.GetString($decryptedBytes)
return $decryptedPassword
'@