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 Import-Module "$PSScriptRoot\..\RunAsHelper.psm1" $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 } catch { Throw-TerminatingError ($LocalizedData.ErrorOpeningExistingFile -f $FileName) $_ } finally { if($existingStream -ne $null) { $existingStream.Dispose() } } $hash2 = $Entry.Checksum 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 ) 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 } 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")) { if($item.LastWriteTime.Equals($entry.ExistingItemTimestamp)) { Trace-Message ($LocalizedData.Notperformingchecksumthefileondiskhasthesamewritetimeasthelasttimeweverifieditscontents) } else { if(-not (Compare-FileToEntry $dest $entry $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) if (IsNanoServer) { add-type -assemblyname System.IO.Compression } else { 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