Last active
August 29, 2015 14:21
-
-
Save guitarrapc/9efbac1c59c8af06ad1c to your computer and use it in GitHub Desktop.
Fix fro MSFT_Archive (WMF5.0) to prevent taking handle with Calculate file CheckSum mode.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Localized 04/23/2015 04:08 AM (GMT) 303:4.80.0411 ArchiveProvider.psd1 | |
# Localized ArchiveProvider.psd1 | |
ConvertFrom-StringData @' | |
###PSLOC | |
InvalidChecksumArgsMessage=Specifying a Checksum without requesting content validation (the Validate parameter) is not meaningful | |
InvalidDestinationDirectory=The specified destination directory {0} does not exist or is not a directory | |
InvalidSourcePath=The specified source file {0} does not exist or is not a file | |
InvalidNetSourcePath=The specified source file {0} is not a valid net source path | |
ErrorOpeningExistingFile=An error occurred while opening the file {0} on disk. Please examine the inner exception for details | |
ErrorOpeningArchiveFile=An error occurred while opening the archive file {0}. Please examine the inner exception for details | |
ItemExistsButIsWrongType=The named item ({0}) exists but is not the expected type, and Force was not specified | |
ItemExistsButIsIncorrect=The destination file {0} has been determined not to match the source, but Force has not been specified. Cannot continue | |
ErrorCopyingToOutstream=An error was encountered while copying the archived file to {0} | |
PackageUninstalled=The archive at {0} was removed from destination {1} | |
PackageInstalled=The archive at {0} was unpacked to destination {1} | |
ConfigurationStarted=The configuration of MSFT_ArchiveResource is starting | |
ConfigurationFinished=The configuration of MSFT_ArchiveResource has completed | |
MakeDirectory=Make directory {0} | |
RemoveFileAndRecreateAsDirectory=Remove existing file {0} and replace it with a directory of the same name | |
RemoveFile=Remove file {0} | |
RemoveDirectory=Remove directory {0} | |
UnzipFile=Unzip archived file to {0} | |
DestMissingOrIncorrectTypeReason=The destination file {0} was missing or was not a file | |
DestHasIncorrectHashvalue=The destination file {0} exists but its checksum did not match the origin file | |
DestShouldNotBeThereReason=The destination file {0} exists but should not | |
UsingKeyToRetrieveHashValue=Using {0} to retrieve hash value | |
Nocachevaluefound=No cache value found | |
Cachevaluefoundreturning=Cache value found, returning {0} | |
CacheCorrupt=Cache found, but failed to loaded. Ignoring Cache. | |
Usingtmpkeytosavehashvalue=Using {0} {1} to save hash value | |
AbouttocachevalueInputObject=About to cache value {0} | |
InUpdateCache=In Update-Cache | |
AddingentryFullNameasacacheentry=Adding {0} as a cache entry | |
UpdatingCacheObject=Updating CacheObject | |
Placednewcacheentry=Placed new cache entry | |
NormalizeChecksumreturningChecksum=Normalize-Checksum returning {0} | |
PathPathisalreadyaccessiableNomountneeded.=Path {0} is already accessible. No mount needed. | |
Pathpathisnotavalidatenetpath=Path {0} is not a validate net path. | |
createpsdrivewithPathpath=create psdrive with Path {0}... | |
CannotaccessPathPathwithgivenCredential=Cannot access Path {0} with given Credential | |
Abouttovalidatestandardarguments=About to validate standard arguments | |
Goingforcacheentries=Going for cache entries | |
Thecachewasuptodateusingcachetosatisfyrequests=The cache was up to date, using cache to satisfy requests | |
Abouttoopenthezipfile=About to open the zip file | |
Cacheupdatedwithentries=Cache updated with {0} entries | |
Processing=Processing {0} | |
InTestTargetResourcedestexistsnotusingchecksumscontinuing=In Test-TargetResource: {0} exists, not using checksums, continuing | |
Notperformingchecksumthefileondiskhasthesamewritetimeasthelasttimeweverifieditscontents=Not performing checksum, the file on disk has the same write time as the last time we verified its contents | |
destexistsandthehashmatcheseven={0} exists and the hash matches even though the LastModifiedTime did not. Updating cache | |
InTestTargetResourcedestexistsandtheselectedtimestampChecksummatched=In Test-TargetResource: {0} exists and the selected timestamp {1} matched | |
RemovePSDriveonRootpsdriveRoot=Remove PSDrive on Root {0} | |
RemovingDir=Removing {0} | |
Hashesofexistingandzipfilesmatchremoving=Hashes of existing and zip files match, removing | |
HashdidnotmatchfilehasbeenmodifiedsinceitwasextractedLeaving=Hash did not match, file has been modified since it was extracted. Leaving | |
InSetTargetResourceexistsselectedtimestampmatched=In Set-TargetResource: {0} exists and the selected timestamp {1} matched, removing | |
InSetTargetResourceexistsdtheselectedtimestampnotmatchg=In Set-TargetResource: {0} exists and the selected timestamp {1} did not match, leaving | |
existingappearstobeanemptydirectoryRemovingit={0} appears to be an empty directory. Removing it | |
LastWriteTimemtcheswhatwehaverecordnotreexaminingchecksum=LastWriteTime of {0} matches what we have on record, not re-examining {1} | |
FoundfatdestwheregoingtoplaceoneandhashmatchedContinuing=Found a file at {0} where we were going to place one and hash matched. Continuing | |
FoundfileatdestwhereweweregoingtoplaceoneandhashdidntmatchItwillbeoverwritten=Found a file at $dest where we were going to place one and hash did not match. It will be overwritten | |
FoundfileatdestwhereweweregoingtoplaceoneanddoesnotmatchthesourcebutForcewasnotspecifiedErroring=Found a file at {0} where we were going to place one and does not match the source, but Force was not specified. Erroring | |
InSetTargetResourcedestexistsandtheselectedtimestamp$ChecksumdidnotmatchForcewasspecifiedwewilloverwrite="In Set-TargetResource: {0} exists and the selected timestamp {1} did not match. Force was specified, we will overwrite | |
FoundafileatdestandtimestampChecksumdoesnotmatchthesourcebutForcewasnotspecifiedErroring=Found a file at {0} and timestamp {1} does not match the source, but Force was not specified. Erroring | |
FoundadirectoryatdestwhereafileshouldbeRemoving=Found a directory at {0} where a file should be. Removing | |
FounddirectoryatdestwhereafileshouldbeandForcewasnotspecifiedErroring=Found a directory at {0} where a file should be and Force was not specified. Erroring. | |
Writingtofiledest=Writing to file {0} | |
RemovePSDriveonRootdriveRoot=Remove PSDrive on Root {0} | |
Updatingcache=Updating cache | |
FolderDirdoesnotexist=Folder {0} does not exist | |
Examiningdirectorytoseeifitshouldberemoved=Examining {0} to see if it should be removed | |
InSetTargetResourcedestexistsandtheselectedtimestampChecksummatchedwillleaveit=In Set-TargetResource: {0} exists and the selected timestamp {1} matched, will leave it | |
###PSLOC | |
'@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data LocalizedData | |
{ | |
# culture="en-US" | |
# TODO: Support WhatIf | |
ConvertFrom-StringData @' | |
InvalidChecksumArgsMessage=Specifying a Checksum without requesting content validation (the Validate parameter) is not meaningful | |
InvalidDestinationDirectory=The specified destination directory {0} does not exist or is not a directory | |
InvalidSourcePath=The specified source file {0} does not exist or is not a file | |
InvalidNetSourcePath=The specified source file {0} is not a valid net source path | |
ErrorOpeningExistingFile=An error occurred while opening the file {0} on disk. Please examine the inner exception for details | |
ErrorOpeningArchiveFile=An error occurred while opening the archive file {0}. Please examine the inner exception for details | |
ItemExistsButIsWrongType=The named item ({0}) exists but is not the expected type, and Force was not specified | |
ItemExistsButIsIncorrect=The destination file {0} has been determined not to match the source, but Force has not been specified. Cannot continue | |
ErrorCopyingToOutstream=An error was encountered while copying the archived file to {0} | |
PackageUninstalled=The archive at {0} was removed from destination {1} | |
PackageInstalled=The archive at {0} was unpacked to destination {1} | |
ConfigurationStarted=The configuration of MSFT_ArchiveResource is starting | |
ConfigurationFinished=The configuration of MSFT_ArchiveResource has completed | |
MakeDirectory=Make directory {0} | |
RemoveFileAndRecreateAsDirectory=Remove existing file {0} and replace it with a directory of the same name | |
RemoveFile=Remove file {0} | |
RemoveDirectory=Remove directory {0} | |
UnzipFile=Unzip archived file to {0} | |
DestMissingOrIncorrectTypeReason=The destination file {0} was missing or was not a file | |
DestHasIncorrectHashvalue=The destination file {0} exists but its checksum did not match the origin file | |
DestShouldNotBeThereReason=The destination file {0} exists but should not | |
UsingKeyToRetrieveHashValue = Using {0} to retrieve hash value | |
Nocachevaluefound = No cache value found | |
Cachevaluefoundreturning = Cache value found, returning {0} | |
CacheCorrupt = Cache found, but failed to loaded. Ignoring Cache. | |
Usingtmpkeytosavehashvalue = Using {0} {1} to save hash value | |
AbouttocachevalueInputObject = About to cache value {0} | |
InUpdateCache = In Update-Cache | |
AddingentryFullNameasacacheentry = Adding {0} as a cache entry | |
UpdatingCacheObject = Updating CacheObject | |
Placednewcacheentry = Placed new cache entry | |
NormalizeChecksumreturningChecksum = Normalize-Checksum returning {0} | |
PathPathisalreadyaccessiableNomountneeded. = Path {0} is already accessible. No mount needed. | |
Pathpathisnotavalidatenetpath = Path {0} is not a validate net path. | |
createpsdrivewithPathpath = create psdrive with Path {0}... | |
CannotaccessPathPathwithgivenCredential = Cannot access Path {0} with given Credential | |
Abouttovalidatestandardarguments = About to validate standard arguments | |
Goingforcacheentries = Going for cache entries | |
Thecachewasuptodateusingcachetosatisfyrequests = The cache was up to date, using cache to satisfy requests | |
Abouttoopenthezipfile = About to open the zip file | |
Cacheupdatedwithentries = Cache updated with {0} entries | |
Processing = Processing {0} | |
InTestTargetResourcedestexistsnotusingchecksumscontinuing = In Test-TargetResource: {0} exists, not using checksums, continuing | |
Notperformingchecksumthefileondiskhasthesamewritetimeasthelasttimeweverifieditscontents = Not performing checksum, the file on disk has the same write time as the last time we verified its contents | |
destexistsandthehashmatcheseven = {0} exists and the hash matches even though the LastModifiedTime did not. Updating cache | |
InTestTargetResourcedestexistsandtheselectedtimestampChecksummatched = In Test-TargetResource: {0} exists and the selected timestamp {1} matched | |
RemovePSDriveonRootpsdriveRoot = Remove PSDrive on Root {0} | |
RemovingDir = Removing {0} | |
Hashesofexistingandzipfilesmatchremoving = Hashes of existing and zip files match, removing | |
HashdidnotmatchfilehasbeenmodifiedsinceitwasextractedLeaving = Hash did not match, file has been modified since it was extracted. Leaving | |
InSetTargetResourceexistsselectedtimestampmatched = In Set-TargetResource: {0} exists and the selected timestamp {1} matched, removing | |
InSetTargetResourceexistsdtheselectedtimestampnotmatchg = In Set-TargetResource: {0} exists and the selected timestamp {1} did not match, leaving | |
existingappearstobeanemptydirectoryRemovingit = {0} appears to be an empty directory. Removing it | |
LastWriteTimemtcheswhatwehaverecordnotreexaminingchecksum = LastWriteTime of {0} matches what we have on record, not re-examining {1} | |
FoundfatdestwheregoingtoplaceoneandhashmatchedContinuing = Found a file at {0} where we were going to place one and hash matched. Continuing | |
FoundfileatdestwhereweweregoingtoplaceoneandhashdidntmatchItwillbeoverwritten = Found a file at $dest where we were going to place one and hash did not match. It will be overwritten | |
FoundfileatdestwhereweweregoingtoplaceoneanddoesnotmatchthesourcebutForcewasnotspecifiedErroring = Found a file at {0} where we were going to place one and does not match the source, but Force was not specified. Erroring | |
InSetTargetResourcedestexistsandtheselectedtimestamp$ChecksumdidnotmatchForcewasspecifiedwewilloverwrite = "In Set-TargetResource: {0} exists and the selected timestamp {1} did not match. Force was specified, we will overwrite | |
FoundafileatdestandtimestampChecksumdoesnotmatchthesourcebutForcewasnotspecifiedErroring = Found a file at {0} and timestamp {1} does not match the source, but Force was not specified. Erroring | |
FoundadirectoryatdestwhereafileshouldbeRemoving = Found a directory at {0} where a file should be. Removing | |
FounddirectoryatdestwhereafileshouldbeandForcewasnotspecifiedErroring = Found a directory at {0} where a file should be and Force was not specified. Erroring. | |
Writingtofiledest = Writing to file {0} | |
RemovePSDriveonRootdriveRoot = Remove PSDrive on Root {0} | |
Updatingcache = Updating cache | |
FolderDirdoesnotexist = Folder {0} does not exist | |
Examiningdirectorytoseeifitshouldberemoved = Examining {0} to see if it should be removed | |
InSetTargetResourcedestexistsandtheselectedtimestampChecksummatchedwillleaveit = In Set-TargetResource: {0} exists and the selected timestamp {1} matched, will leave it | |
'@ | |
} | |
Import-LocalizedData LocalizedData -filename ArchiveProvider.psd1 | |
$Debug = $false | |
Function Trace-Message | |
{ | |
param([string] $Message) | |
if($Debug) | |
{ | |
Write-Verbose $Message | |
} | |
} | |
$CacheLocation = "$env:systemroot\system32\Configuration\BuiltinProvCache\MSFT_ArchiveResource" | |
Function DSCHashNameToPSHashName | |
{ | |
param( | |
[string] $Algorithm | |
) | |
$dscToPsName= @{ | |
# Since in-box powershell Get-FileHash cmdlet takes algorithm without hypen, we silently change the names | |
"sha-1" = "SHA1" | |
"sha-256" = "SHA256" | |
"sha-512" = "SHA512" | |
} | |
$psName = $dscToPsName[$Algorithm] | |
$psName | |
} | |
Function Get-CacheEntry | |
{ | |
param( | |
[string]$path, | |
[string]$destination | |
) | |
$key = ($path + $destination).GetHashCode() | |
Trace-Message ($LocalizedData.UsingKeyToRetrieveHashValue -f $Key) | |
$path = Join-Path $CacheLocation $key | |
if(-not (Test-Path $path)) | |
{ | |
Trace-Message ($LocalizedData.Nocachevaluefound) | |
return @{} | |
} | |
else | |
{ | |
# ErroAction seems to have no affect on this condition, (see: https://microsoft.visualstudio.com/web/wi.aspx?pcguid=cb55739e-4afe-46a3-970f-1b49d8ee7564&id=1185735) | |
# using a try/catch to work around the issue. | |
try | |
{ | |
$tmp = Import-CliXml $path | |
Trace-Message ($LocalizedData.Cachevaluefoundreturning -f $tmp) | |
return $tmp | |
} | |
catch [System.Xml.XmlException] | |
{ | |
Trace-Message ($LocalizedData.CacheCorrupt) | |
return @{} | |
} | |
} | |
} | |
Function Set-CacheEntry | |
{ | |
param( | |
[object] $InputObject, | |
[string] $path, | |
[string] $destination | |
) | |
$key = ($path + $destination).GetHashCode() | |
Trace-Message ($LocalizedData.Usingtmpkeytosavehashvalue -f $tmp, $key) | |
$path = Join-Path $CacheLocation $key | |
Trace-Message ($LocalizedData.AbouttocachevalueInputObject -f $InputObject) | |
if(-not (Test-Path $CacheLocation)) | |
{ | |
mkdir $CacheLocation | Out-Null | |
} | |
Export-CliXml -Path $path -InputObject $InputObject | |
} | |
Function Throw-InvalidArgumentException | |
{ | |
param( | |
[string] $Message, | |
[string] $ParamName | |
) | |
$exception = new-object System.ArgumentException $Message,$ParamName | |
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,$ParamName,"InvalidArgument",$null | |
throw $errorRecord | |
} | |
Function Throw-TerminatingError | |
{ | |
param( | |
[string] $Message, | |
[System.Management.Automation.ErrorRecord] $ErrorRecord, | |
[string] $ExceptionType | |
) | |
$exception = new-object "System.InvalidOperationException" $Message,$ErrorRecord.Exception | |
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"MachineStateIncorrect","InvalidOperation",$null | |
throw $errorRecord | |
} | |
Function Assert-ValidStandardArgs | |
{ | |
param( | |
[string] $Path, | |
[string] $Destination, | |
[boolean] $Validate, | |
[string] $Checksum | |
) | |
#mkdir and Test-Path can each fail with a useful error message | |
#We want to stop our execution if that happens | |
$ErrorActionPreference = "Stop" | |
if(-not (Test-Path -PathType Leaf $Path)) | |
{ | |
Throw-InvalidArgumentException ($LocalizedData.InvalidSourcePath -f $Path) "Path" | |
} | |
$item = Get-Item -EA Ignore $Destination | |
if($item -and $item.GetType() -eq [System.IO.FileInfo]) | |
{ | |
Throw-InvalidArgumentException ($LocalizedData.InvalidDestinationDirectory -f $Destination) "Destination" | |
} | |
if($Checksum -and -not $Validate) | |
{ | |
Throw-InvalidArgumentException ($LocalizedData.InvalidChecksumArgsMessage -f $Checksum) "Checksum" | |
} | |
} | |
Function Compare-FileToEntry | |
{ | |
param( | |
[string] $FileName, | |
[object] $Entry, | |
[string] $Algorithm | |
) | |
$existingStream = $null | |
$hash1 = $null | |
try | |
{ | |
#$existingStream = New-Object System.IO.FileStream $FileName, "Open" | |
#$psAlgorithm = DSCHashNameToPSHashName -Algorithm $Algorithm | |
#$hash1 = Get-FileHash -InputStream $existingStream -Algorithm $psAlgorithm | |
$psAlgorithm = DSCHashNameToPSHashName -Algorithm $Algorithm | |
$hash1 = Get-FileHash -Path $FileName -Algorithm $psAlgorithm | |
Write-Debug $FileName | |
} | |
catch | |
{ | |
Throw-TerminatingError ($LocalizedData.ErrorOpeningExistingFile -f $FileName) $_ | |
} | |
finally | |
{ | |
if($existingStream -ne $null) | |
{ | |
$existingStream.Dispose() | |
} | |
} | |
$hash2 = $Entry.Checksum | |
$hash1 | ft | Out-String -Stream | Write-Debug | |
$hash2 | ft | Out-String -Stream | Write-Debug | |
return ($hash1.Algorithm -eq $hash2.Algorithm) -and ($hash1.Hash -eq $hash2.Hash) | |
} | |
Function Get-RelevantChecksumTimestamp | |
{ | |
param( | |
[System.IO.FileSystemInfo] $FileSystemObject, | |
[String] $Checksum | |
) | |
if($Checksum.Equals("createddate")) | |
{ | |
return $FileSystemObject.CreationTime | |
} | |
else #$Checksum.Equals("modifieddate") | |
{ | |
return $FileSystemObject.LastWriteTime | |
} | |
} | |
Function Update-Cache | |
{ | |
param( | |
[Hashtable] $CacheObject, | |
[System.IO.Compression.ZipArchiveEntry[]] $Entries, | |
[string] $Checksum, | |
[string] $SourceLastWriteTime | |
) | |
Function Get-Hash | |
{ | |
param( | |
[System.IO.Stream] $Stream, | |
[string] $Algorithm | |
) | |
$hashGenerator = $null | |
$hashNameToType = @{ | |
#This is the sort of thing that normally is declared as a global constant, but referencing the type incurs cost | |
#that we're trying to avoid in some cold-start cases | |
"sha-1" = [System.Security.Cryptography.SHA1] | |
"sha-256" = [System.Security.Cryptography.SHA256]; | |
"sha-512" = [System.Security.Cryptography.SHA512] | |
} | |
$algorithmType = $hashNameToType[$Algorithm] | |
try | |
{ | |
$hashGenerator = $algorithmType::Create() #All types in the dictionary will have this method | |
#[byte[]]$hashGenerator.ComputeHash($Stream) | |
$hash = [System.BitConverter]::ToString($hashGenerator.ComputeHash($Stream)).Replace("-", "") | |
return [PSCustomObject]@{ | |
Hash = $hash | |
Algorithm = DSCHashNameToPSHashName -Algorithm $Algorithm | |
} | |
} | |
finally | |
{ | |
if($hashGenerator) | |
{ | |
$hashGenerator.Dispose() | |
} | |
} | |
} | |
Trace-Message ($LocalizedData.InUpdateCache) | |
$cacheEntries = new-object System.Collections.ArrayList | |
foreach($entry in $Entries) | |
{ | |
$hash = $null | |
if($Checksum.StartsWith("sha")) | |
{ | |
$stream = $null | |
try | |
{ | |
$stream = $entry.Open() | |
#$psAlgorithm = DSCHashNameToPSHashName -Algorithm $Checksum | |
#$hash = Get-FileHash -InputStream $stream -Algorithm $psAlgorithm | |
$hash = Get-Hash -Stream $stream -Algorithm $Checksum | |
} | |
finally | |
{ | |
if($stream) | |
{ | |
$stream.Dispose() | |
} | |
} | |
} | |
Trace-Message ($LoalizedData.AddingentryFullNameasacacheentry -f $entry.FullName) | |
$cacheEntries.Add(@{ | |
FullName = $entry.FullName | |
LastWriteTime = $entry.LastWriteTime | |
Checksum = $hash | |
}) | Out-Null | |
} | |
Trace-Message ($LocalizedData.UpdatingCacheObject) | |
$CacheObject["SourceLastWriteTime"] = $SourceLastWriteTime | |
$CacheObject["Entries"] = (@() + $cacheEntries) | |
Set-CacheEntry -InputObject $CacheObject -path $Path -destination $Destination | |
Trace-Message ($LocalizedData.Placednewcacheentry) | |
} | |
Function Normalize-Checksum | |
{ | |
param( | |
[boolean] $Validate, | |
[string] $Checksum | |
) | |
if($Validate) | |
{ | |
if(-not $Checksum) | |
{ | |
$Checksum = "SHA-256" | |
} | |
$Checksum = $Checksum.ToLower() | |
} | |
Trace-Message ($LocalizedData.NormalizeChecksumreturningChecksum -f $Checksum) | |
return $Checksum | |
} | |
# create a psdrive to a net share with a given credential. | |
function Mount-NetworkPath | |
{ | |
param ( [string] $path, [pscredential] $Credential) | |
# mount the drive only if not accessible | |
if (Test-Path $path -ErrorAction Ignore) | |
{ | |
Trace-Message ($LocalizedData.PathPathisalreadyaccessiableNomountneeded -f $Path) | |
$psdrive = $null | |
} | |
else | |
{ | |
if (-not $path.EndsWith("\")) | |
{ | |
$index = $Path.LastIndexOf("\"); | |
if (-1 -eq $index) | |
{ | |
Trace-Message ($LocalizedData.Pathpathisnotavalidatenetpath -f $path) | |
Throw-TerminatingError ($LocalizedData.InvalidNetSourcePath -f $Path) | |
} | |
else | |
{ | |
$path = $path.Substring(0, $index) | |
} | |
} | |
$psdriveArgs = @{ Name=([guid]::NewGuid()); PSProvider="FileSystem"; Root=$Path; Scope="Script"; Credential=$Credential } | |
try | |
{ | |
Trace-Message ($LocalizedData.createpsdrivewithPathpath -f $path) | |
$psdrive = New-PSDrive @psdriveArgs | |
} | |
catch | |
{ | |
Trace-Message ($LocalizedData.CannotaccessPathPathwithgivenCredential -f $Path) | |
Throw-TerminatingError ($LocalizedData.ErrorOpeningArchiveFile -f $Path) $_ | |
} | |
} | |
return $psdrive | |
} | |
# The Test-TargetResource cmdlet is used to test the status of item on the destination | |
function Test-TargetResource | |
{ | |
param | |
( | |
[ValidateSet("Present", "Absent")] | |
[string] $Ensure = "Present", | |
[parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] $Path, | |
[parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] $Destination, | |
[boolean] $Validate = $false, | |
[ValidateSet("", "SHA-1", "SHA-256", "SHA-512", "CreatedDate", "ModifiedDate")] | |
[string] $Checksum, | |
[boolean] $Force = $false, | |
[pscredential] $Credential | |
) | |
if ($Credential) | |
{ | |
$psdrive = Mount-NetworkPath -path $Path -Credential $Credential | |
} | |
try | |
{ | |
$ErrorActionPreference = "Stop" | |
Trace-Message ($LocalizedData.Abouttovalidatestandardarguments) | |
Assert-ValidStandardArgs $Path $Destination $Validate $Checksum | |
$Checksum = Normalize-Checksum $Validate $Checksum | |
Trace-Message ($LocalizedData.Goingforcacheentries) | |
$result = $true | |
$cacheObj = Get-CacheEntry -path $Path -destination $Destination | |
$sourceLastWriteTime = (Get-Item $Path).LastWriteTime | |
$cacheUpToDate = $cacheObj -and $cacheObj.SourceLastWriteTime -and $cacheObj.SourceLastWriteTime -eq $sourceLastWriteTime | |
$file = $null | |
try | |
{ | |
$entries = $null | |
if($cacheUpToDate) | |
{ | |
Trace-Message ($LocalizedData.Thecachewasuptodateusingcachetosatisfyrequests) | |
$entries = $cacheObj.Entries | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.Abouttoopenthezipfile) | |
$entries, $null, $file = Open-ZipFile $Path | |
Trace-Message ($LocalizedData.Updatingcache) | |
Update-Cache $cacheObj $entries $Checksum $sourceLastWriteTime | |
$entries = $cacheObj.Entries | |
Trace-Message ($LocalizedData.Cacheupdatedwithentries -f $cacheObj.Entries.Length) | |
} | |
foreach($entry in $entries) | |
{ | |
$individualResult = $true | |
Trace-Message ($LocalizedData.Processing -f $entry.FullName) | |
$dest = join-path $Destination $entry.FullName | |
if($dest.EndsWith('\')) #Directory | |
{ | |
$dest = $dest.TrimEnd('\') | |
if(-not (Test-Path -PathType Container $dest)) | |
{ | |
Write-Verbose ($LocalizedData.DestMissingOrIncorrectTypeReason -f $dest) | |
$individualResult = $result = $false | |
} | |
} | |
else | |
{ | |
$item = Get-Item $dest -EA Ignore | |
if(-not $item) | |
{ | |
$individualResult = $result = $false | |
} | |
elseif($item.GetType() -ne [System.IO.FileInfo]) | |
{ | |
$individualResult = $result = $false | |
} | |
if(-not $Checksum) | |
{ | |
Trace-Message ($LocalizedData.InTestTargetResourcedestexistsnotusingchecksumscontinuing -f $dest) | |
if(-not $individualResult -and $Ensure -eq "Present") | |
{ | |
Write-Verbose ($LocalizedData.DestMissingOrIncorrectTypeReason -f $dest) | |
} | |
elseif($individualResult -and $Ensure -eq "Absent") | |
{ | |
Write-Verbose ($LocalizedData.DestShouldNotBeThereReason -f $dest) | |
} | |
} | |
else | |
{ | |
#If the file is there we need to check if it could possibly fail in a different way | |
#Otherwise we skip all these checks - there's nothing to work with | |
if($individualResult) | |
{ | |
$Checksum = $Checksum.ToLower() | |
if($Checksum.StartsWith("sha")) | |
{ | |
Write-Debug "sha" | |
if($item.LastWriteTime.Equals($entry.ExistingItemTimestamp)) | |
{ | |
Write-Debug "Check Date" | |
Trace-Message ($LocalizedData.Notperformingchecksumthefileondiskhasthesamewritetimeasthelasttimeweverifieditscontents) | |
} | |
else | |
{ | |
Write-Debug "Start Compare" | |
if(-not (Compare-FileToEntry -FileName $dest -Entry $entry -Algorithm $Checksum)) | |
{ | |
$individualResult = $result = $false | |
} | |
else | |
{ | |
$entry.ExistingItemTimestamp = $item.LastWriteTime | |
Trace-Message ($LocalizedData.destexistsandthehashmatcheseven -f $dest) | |
} | |
} | |
} | |
else | |
{ | |
$date = Get-RelevantChecksumTimestamp $item $Checksum | |
if(-not $date.Equals($entry.LastWriteTime.DateTime)) | |
{ | |
$individualResult = $result = $false | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.InTestTargetResourcedestexistsandtheselectedtimestampChecksummatched -f $dest, $Checksum) | |
} | |
} | |
} | |
if(-not $individualResult -and $Ensure -eq "Present") | |
{ | |
Write-Verbose ($LocalizedData.DestHasIncorrectHashvalue -f $dest) | |
} | |
elseif($individualResult -and $Ensure -eq "Absent") | |
{ | |
Write-Verbose ($LocalizedData.DestShouldNotBeThereReason -f $dest) | |
} | |
} | |
} | |
} | |
} | |
finally | |
{ | |
if($file) | |
{ | |
$file.Dispose() | |
} | |
} | |
Set-CacheEntry -InputObject $cacheObj -path $Path -destination $Destination | |
$result = $result -eq ("Present" -eq $Ensure) | |
} | |
finally | |
{ | |
if($psdrive) | |
{ | |
$ErrorActionPreference = "SilentlyContinue" | |
Trace-Message ($LoalizedData.RemovePSDriveonRootpsdrive -f $($psdrive.Root)) | |
Remove-PSDrive $psdrive -Force -ErrorAction SilentlyContinue | |
} | |
} | |
return $result | |
} | |
Function Ensure-Directory | |
{ | |
param([string] $Dir) | |
$item = get-item $Dir -EA SilentlyContinue | |
if(-not $item) | |
{ | |
Trace-Message ($LocalizedData.FolderDirdoesnotexist -f $Dir) | |
if($PSCmdlet.ShouldProcess(($LocalizedData.MakeDirectory -f $Dir), $null, $null)) | |
{ | |
mkdir $Dir | Out-Null | |
} | |
} | |
else | |
{ | |
if($item.GetType() -ne [System.IO.DirectoryInfo]) | |
{ | |
if($Force -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFileAndRecreateAsDirectory -f $Dir), $null, $null)) | |
{ | |
Trace-Message ($LocalizedData.RemovingDir -f $Dir) | |
rm $Dir | Out-Null | |
mkdir $Dir | Out-Null #Note that we don't do access time translations onto directories since we are emulating the shell's behavior | |
} | |
else | |
{ | |
Throw-TerminatingError ($LocalizedData.ItemExistsButIsWrongType -f $Path) | |
} | |
} | |
} | |
} | |
Function Open-ZipFile | |
{ | |
param($Path) | |
add-type -assemblyname System.IO.Compression.FileSystem | |
$nameHash = @{} | |
try | |
{ | |
$fileHandle = ([System.IO.Compression.ZipFile]::OpenRead($Path)) | |
$entries = $fileHandle.Entries | |
} | |
catch | |
{ | |
Throw-TerminatingError ($LocalizedData.ErrorOpeningArchiveFile -f $Path) $_ | |
} | |
$entries | %{$nameHash[$_.FullName] = $_} | |
return $entries, $nameHash, $fileHandle | |
} | |
# The Set-TargetResource cmdlet is used to unpack or remove a zip file to a particular directory | |
function Set-TargetResource | |
{ | |
[CmdletBinding(SupportsShouldProcess=$true)] | |
param | |
( | |
[parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] $Path, | |
[parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] $Destination, | |
[ValidateSet("Present", "Absent")] | |
[string] $Ensure = "Present", | |
[boolean] $Validate = $false, | |
[ValidateSet("", "SHA-1", "SHA-256", "SHA-512", "CreatedDate", "ModifiedDate")] | |
[string] $Checksum, | |
[boolean] $Force = $false, | |
[pscredential] $Credential | |
) | |
if ($Credential) | |
{ | |
$psdrive = Mount-NetworkPath -path $Path -Credential $Credential | |
} | |
try | |
{ | |
$ErrorActionPreference = "Stop" | |
Assert-ValidStandardArgs $Path $Destination $Validate $Checksum | |
$Checksum = Normalize-Checksum $Validate $Checksum | |
Write-Verbose $LocalizedData.ConfigurationStarted | |
if(-not (Test-Path $Destination)) | |
{ | |
mkdir $Destination | Out-Null | |
} | |
$cacheObj = Get-CacheEntry -path $Path -destination $Destination | |
$sourceLastWriteTime = (Get-Item $Path).LastWriteTime | |
$cacheUpToDate = $cacheObj -and $cacheObj.SourceLastWriteTime -and $cacheObj.SourceLastWriteTime -eq $sourceLastWriteTime | |
$file = $null | |
$nameToEntry = @{} | |
try | |
{ | |
# once cache is updated we need to dispose the handle | |
# if not, when the second time $file is updated with a | |
# new handle, the old one not being disposed will lead | |
# to archive resource having a lock on the file | |
if(-not $cacheUpToDate) | |
{ | |
$entries, $nameToEntry, $file = Open-ZipFile $Path | |
Update-Cache $cacheObj $entries $Checksum $sourceLastWriteTime | |
} | |
} | |
finally | |
{ | |
if($file) | |
{ | |
$file.Dispose() | |
$file = $null | |
} | |
} | |
try | |
{ | |
$entries = $cacheObj.Entries | |
if($Ensure -eq "Absent") | |
{ | |
$directories = new-object system.collections.generic.hashset[string] | |
foreach($entry in $entries) | |
{ | |
$isDir = $false | |
if($entry.FullName.EndsWith("\")) | |
{ | |
$isDir = $true | |
$directories.Add((Split-Path -Leaf $entry)) | Out-Null | |
} | |
$parent = $entry.FullName | |
while(($parent = (Split-Path $parent))) { $directories.Add($parent) | Out-Null } | |
if($isDir) | |
{ | |
#Directory removal is handled as its own pass, see note and code after this loop | |
continue | |
} | |
$existing = Join-Path $Destination $entry.FullName | |
$item = Get-Item $existing -EA SilentlyContinue | |
if(-not $item) | |
{ | |
continue | |
} | |
#Possible for a folder to have been replaced by a directory of the same name, in which case we must leave it alone | |
$type = $item.GetType() | |
if($type -ne [System.IO.FileInfo]) | |
{ | |
continue | |
} | |
if(-not $Checksum -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFile -f $existing), $null, $null)) | |
{ | |
Trace-Message ($LocalizedData.RemovingDir -f $existing) | |
rm $existing | |
continue | |
} | |
$Checksum = $Checksum.ToLower() | |
if($Checksum.StartsWith("sha")) | |
{ | |
if((Compare-FileToEntry $existing $entry $Checksum) -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFile -f $existing), $null, $null)) | |
{ | |
Trace-Message ($LocalizedData.Hashesofexistingandzipfilesmatchremoving) | |
rm $existing | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.HashdidnotmatchfilehasbeenmodifiedsinceitwasextractedLeaving) | |
} | |
} | |
else | |
{ | |
$date = Get-RelevantChecksumTimestamp $item $Checksum | |
if($date.Equals($entry.LastWriteTime.DateTime) -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFile -f $existing), $null, $null)) | |
{ | |
Trace-Message ($LocalizedData.InSetTargetResourceexistsselectedtimestampmatched -f $existing, $Checksum) | |
rm $existing | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.InSetTargetResourceexistsdtheselectedtimestampnotmatchg -f $existing, $Checksum) | |
} | |
} | |
} | |
#Hashset was useful for dropping dupes in an efficient manner, but it can mess with ordering | |
#Sort according to current culture (directory names can be localized, obviously) | |
#Reverse so we hit children before parents | |
$directories = [system.linq.enumerable]::ToList($directories) | |
$directories.Sort([System.StringComparer]::InvariantCultureIgnoreCase) | |
$directories.Reverse() | |
foreach($directory in $directories) | |
{ | |
Trace-Message ($LocalizedData.Examiningdirectorytoseeifitshouldberemoved -f $directory) | |
$existing = Join-Path $Destination $directory | |
$item = Get-Item $existing -EA SilentlyContinue | |
if($item -and $item.GetType() -eq [System.IO.DirectoryInfo] -and $item.GetFiles().Count -eq 0 -and $item.GetDirectories().Count -eq 0 ` | |
-and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveDirectory -f $existing), $null, $null)) | |
{ | |
Trace-Message ($LocalizedData.existingappearstobeanemptydirectoryRemovingit -f $existing) | |
rmdir $existing | |
} | |
} | |
Write-Verbose ($LocalizedData.PackageUninstalled -f $Path,$Destination) | |
Write-Verbose $LocalizedData.ConfigurationFinished | |
return | |
} | |
Ensure-Directory $Destination | |
foreach($entry in $entries) | |
{ | |
$dest = join-path $Destination $entry.FullName | |
if($dest.EndsWith('\')) #Directory | |
{ | |
Ensure-Directory $dest.TrimEnd("\") #Some cmdlets have problems with trailing char | |
continue | |
} | |
$item = Get-Item $dest -EA SilentlyContinue | |
if($item) | |
{ | |
if($item.GetType() -eq [System.IO.FileInfo]) | |
{ | |
if(-not $Checksum) | |
{ | |
#It exists. The user didn't specify -Validate, so that's good enough for us | |
continue | |
} | |
if($Checksum.StartsWith("sha")) | |
{ | |
if($item.LastWriteTime.Equals($entry.ExistingTimestamp)) | |
{ | |
Trace-Message ($LocalizedData.LastWriteTimemtcheswhatwehaverecordnotreexaminingchecksum -f $dest, $checksum) | |
} | |
else | |
{ | |
$identical = Compare-FileToEntry $dest $entry $Checksum | |
if($identical) | |
{ | |
Trace-Message ($LocalizedData.FoundfatdestwheregoingtoplaceoneandhashmatchedContinuing -f $dest) | |
$entry.ExistingItemTimestamp = $item.LastWriteTime | |
continue | |
} | |
else | |
{ | |
if($Force) | |
{ | |
Trace-Message ($LocalizedData.FoundfileatdestwhereweweregoingtoplaceoneandhashdidntmatchItwillbeoverwritten -f $dest) | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.FoundfileatdestwhereweweregoingtoplaceoneanddoesnotmatchthesourcebutForcewasnotspecifiedErroring -f $dest) | |
Throw-TerminatingError ($LocalizedData.ItemExistsButIsIncorrect -f $dest) | |
} | |
} | |
} | |
} | |
else | |
{ | |
$date = Get-RelevantChecksumTimestamp $item $Checksum | |
if($date.Equals($entry.LastWriteTime.DateTime)) | |
{ | |
Trace-Message ($LocalizedData.InSetTargetResourcedestexistsandtheselectedtimestampChecksummatchedwillleaveit -f $dest, $Checksum) | |
continue | |
} | |
else | |
{ | |
if($Force) | |
{ | |
Trace-Message ($LocalizedData.InSetTargetResourcedestexistsandtheselectedtimestamp -f $dest, $Checksum) | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.FoundafileatdestandtimestampChecksumdoesnotmatchthesourcebutForcewasnotspecifiedErroring -f $dest, $Checksum) | |
Throw-TerminatingError ($LocalizedData.ItemExistsButIsIncorrect -f $dest) | |
} | |
} | |
} | |
} | |
else | |
{ | |
if($Force) | |
{ | |
Trace-Message ($LocalizedData.FoundadirectoryatdestwhereafileshouldbeRemoving -f $dest) | |
if($PSCmdlet.ShouldProcess(($LocalizedData.RemoveDirectory -f $dest), $null, $null)) | |
{ | |
rmdir -Recurse -Force $dest | |
} | |
} | |
else | |
{ | |
Trace-Message ($LocalizedData.FounddirectoryatdestwhereafileshouldbeandForcewasnotspecifiedErroring -f $dest) | |
Throw-TerminatingError ($LocalizedData.ItemExistsButIsWrongType -f $dest) | |
} | |
} | |
} | |
$parent = Split-Path $dest | |
if(-not (Test-Path $parent) -and $PSCmdlet.ShouldProcess(($LocalizedData.MakeDirectory -f $parent), $null, $null)) | |
{ | |
#TODO: This is an edge case we need to revisit. We should be correctly handling wrong file types along | |
#the directory path if they occur within the archive, but they don't have to. Simple tests demonstrate that | |
#the Zip format allows you to have the file within a folder without explicitly having an entry for the folder | |
#This solution will fail in such a case IF anything along the path is of the wrong type (e.g. file in a place | |
#we expect a directory to be) | |
mkdir $parent | Out-Null | |
} | |
if($PSCmdlet.ShouldProcess(($LocalizedData.UnzipFile -f $dest), $null, $null)) | |
{ | |
#If we get here we can safely blow away anything we find. | |
$null, $nameToEntry, $file = Open-ZipFile $Path | |
$stream = $null | |
$outStream = $null | |
try | |
{ | |
Trace-Message ($LocalizedData.Writingtofiledest -f $dest) | |
$stream = $nameToEntry[$entry.FullName].Open() | |
$outStream = New-Object System.IO.FileStream $dest, "Create" | |
$stream.CopyTo($outStream) | |
} | |
catch | |
{ | |
Throw-TerminatingError ($LocalizedData.ErrorCopyingToOutstream -f $dest) $_ | |
} | |
finally | |
{ | |
if($stream -ne $null) | |
{ | |
$stream.Dispose() | |
} | |
if($outStream -ne $null) | |
{ | |
$outStream.Dispose() | |
} | |
} | |
$fileInfo = New-Object System.IO.FileInfo $dest | |
$entry.ExistingItemTimestamp = $fileInfo.LastWriteTime = $fileInfo.LastAccessTime = $fileInfo.CreationTime = $entry.LastWriteTime.DateTime | |
} | |
} | |
} | |
finally | |
{ | |
if($file) | |
{ | |
$file.Dispose() | |
} | |
} | |
Set-CacheEntry -InputObject $cacheObj -path $Path -destination $Destination | |
Write-Verbose ($LocalizedData.PackageInstalled -f $Path,$Destination) | |
Write-Verbose $LocalizedData.ConfigurationFinished | |
} | |
finally | |
{ | |
if($psdrive) | |
{ | |
$ErrorActionPreference = "SilentlyContinue" | |
Trace-Message ($LocalizedData.RemovePSDriveonRootdriveRoot -f ($($psdrive.Root))) | |
Remove-PSDrive $psdrive -Force -ErrorAction SilentlyContinue | |
} | |
} | |
} | |
# The Get-TargetResource cmdlet is used to fetch the object | |
function Get-TargetResource | |
{ | |
param | |
( | |
[parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] $Path, | |
[parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] $Destination, | |
[boolean] $Validate = $false, | |
[ValidateSet("", "SHA-1", "SHA-256", "SHA-512", "CreatedDate", "ModifiedDate")] | |
[string] $Checksum, | |
[pscredential] $Credential | |
) | |
if ($null -eq $Credential) | |
{ | |
$PSBoundParameters.Remove("Credential") | |
} | |
$exists = Test-TargetResource @PSBoundParameters | |
$stringResult = "Absent" | |
if($exists) | |
{ | |
$stringResult = "Present" | |
} | |
@{ | |
Ensure = $stringResult; | |
Path = $Path; | |
Destination = $Destination; | |
} | |
} | |
Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ClassVersion("1.0.0.0"), FriendlyName("cArchive")] | |
class cArchive : OMI_BaseResource | |
{ | |
[Key, Description("Specifies the source path of the archive file.")] String Path; | |
[Key, Description("Specifies the location where you want to ensure the archive contents are extracted.")] String DEstination; | |
[Write, Description("Determines whether to check if the content of the archive exists at the Destination. Set this property to Present to ensure the contents exist. Set it to Absent to ensure they do not exist. The default value is Present."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; | |
[Write, Description("Uses the Checksum property to determine if the archive matches the signature. If you specify Checksum without Validate, the configuration will fail. If you specify Validate without Checksum, a SHA-256 checksum is used by default.")] Boolean Validate; | |
[Write, Description("Defines the type to use when determining whether two files are the same. If Checksum is not specified, only the file or directory name is used for comparison. Valid values include: SHA1, SHA256, SHA512, createdDate, modifiedDate, none (default). If you specify Checksum without Validate, the configuration will fail."), ValueMap{"SHA-1","SHA-256","SHA-512","CreateDate","ModifiedDate"}, Values{"SHA-1","SHA-256","SHA-512","CreateDate","ModifiedDate"}] String CheckSum; | |
[Write, Description("Certain file operations (such as overwriting a file or deleting a directory that is not empty) will result in an error. Using the Force property overrides such errors. The default value is False.")] Boolean Force; | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma namespace("\\\\.\\root\\default") | |
instance of __namespace{ name="MS_409";}; | |
#pragma namespace("\\\\.\\root\\default\\MS_409") | |
[AMENDMENT, LOCALE(0x0409)] | |
class MSFT_ArchiveResource : OMI_BaseResource | |
{ | |
[Description("Indicates whether to Ensure that the directory is Present or Absent (default Present)") : Amended] string Ensure; | |
[Key,Description("The zip file to be extracted or removed") : Amended] string Path; | |
[Key,Description("The directory to expand the zip file to") : Amended] string Destination; | |
[Description("Indicates whether the modified date and a checksum (SHA-256 if one is not specified) should be use to validate whether an existing destination file matches the source file") : Amended] boolean Validate; | |
[Description("Indicates how to compare destination in source files. If not specified, no comparison is made. Possible values: SHA-1, SHA-256, SHA-512, CreatedDate, ModifiedDate. Used in Get, or in Set for Ensure=Absent or Ensure=Present with Overwrite") : Amended] string Checksum; | |
[Description("Indicates whether to overwrite existing files if found.") : Amended] boolean Force; | |
[Description("The credentials to be used to access archive from network share Path (if applicable)") : Amended] string Credential; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment