]> insang Git - wevape-lu1_pos.git/blob
91bb46cc3f824b51b203c62aa83042b3e002ec3c
[wevape-lu1_pos.git] /
1 data LocalizedData
2 {
3     # culture="en-US"
4     # TODO: Support WhatIf
5     ConvertFrom-StringData @'
6 InvalidChecksumArgsMessage=Specifying a Checksum without requesting content validation (the Validate parameter) is not meaningful
7 InvalidDestinationDirectory=The specified destination directory {0} does not exist or is not a directory
8 InvalidSourcePath=The specified source file {0} does not exist or is not a file
9 InvalidNetSourcePath=The specified source file {0} is not a valid net source path
10 ErrorOpeningExistingFile=An error occurred while opening the file {0} on disk. Please examine the inner exception for details
11 ErrorOpeningArchiveFile=An error occurred while opening the archive file {0}. Please examine the inner exception for details
12 ItemExistsButIsWrongType=The named item ({0}) exists but is not the expected type, and Force was not specified
13 ItemExistsButIsIncorrect=The destination file {0} has been determined not to match the source, but Force has not been specified. Cannot continue
14 ErrorCopyingToOutstream=An error was encountered while copying the archived file to {0}
15 PackageUninstalled=The archive at {0} was removed from destination {1}
16 PackageInstalled=The archive at {0} was unpacked to destination {1}
17 ConfigurationStarted=The configuration of MSFT_ArchiveResource is starting
18 ConfigurationFinished=The configuration of MSFT_ArchiveResource has completed
19 MakeDirectory=Make directory {0}
20 RemoveFileAndRecreateAsDirectory=Remove existing file {0} and replace it with a directory of the same name
21 RemoveFile=Remove file {0}
22 RemoveDirectory=Remove directory {0}
23 UnzipFile=Unzip archived file to {0}
24 DestMissingOrIncorrectTypeReason=The destination file {0} was missing or was not a file
25 DestHasIncorrectHashvalue=The destination file {0} exists but its checksum did not match the origin file
26 DestShouldNotBeThereReason=The destination file {0} exists but should not
27 UsingKeyToRetrieveHashValue = Using {0} to retrieve hash value
28 Nocachevaluefound = No cache value found
29 Cachevaluefoundreturning = Cache value found, returning {0}
30 CacheCorrupt = Cache found, but failed to loaded.  Ignoring Cache.
31 Usingtmpkeytosavehashvalue = Using {0} {1} to save hash value
32 AbouttocachevalueInputObject = About to cache value {0}
33 InUpdateCache = In Update-Cache
34 AddingentryFullNameasacacheentry = Adding {0} as a cache entry
35 UpdatingCacheObject = Updating CacheObject
36 Placednewcacheentry = Placed new cache entry
37 NormalizeChecksumreturningChecksum = Normalize-Checksum returning {0}
38 PathPathisalreadyaccessiableNomountneeded. = Path {0} is already accessible. No mount needed.
39 Pathpathisnotavalidatenetpath = Path {0} is not a validate net path.
40 createpsdrivewithPathpath = create psdrive with Path {0}...
41 CannotaccessPathPathwithgivenCredential = Cannot access Path {0} with given Credential
42 Abouttovalidatestandardarguments = About to validate standard arguments
43 Goingforcacheentries = Going for cache entries
44 Thecachewasuptodateusingcachetosatisfyrequests = The cache was up to date, using cache to satisfy requests
45 Abouttoopenthezipfile = About to open the zip file
46 Cacheupdatedwithentries = Cache updated with {0} entries
47 Processing = Processing {0}
48 InTestTargetResourcedestexistsnotusingchecksumscontinuing = In Test-TargetResource: {0} exists, not using checksums, continuing
49 Notperformingchecksumthefileondiskhasthesamewritetimeasthelasttimeweverifieditscontents = Not performing checksum, the file on disk has the same write time as the last time we verified its contents
50 destexistsandthehashmatcheseven = {0} exists and the hash matches even though the LastModifiedTime did not. Updating cache
51 InTestTargetResourcedestexistsandtheselectedtimestampChecksummatched = In Test-TargetResource: {0} exists and the selected timestamp {1} matched
52 RemovePSDriveonRootpsdriveRoot = Remove PSDrive on Root {0}
53 RemovingDir = Removing {0}
54 Hashesofexistingandzipfilesmatchremoving = Hashes of existing and zip files match, removing
55 HashdidnotmatchfilehasbeenmodifiedsinceitwasextractedLeaving = Hash did not match, file has been modified since it was extracted. Leaving
56 InSetTargetResourceexistsselectedtimestampmatched = In Set-TargetResource: {0} exists and the selected timestamp {1} matched, removing
57 InSetTargetResourceexistsdtheselectedtimestampnotmatchg = In Set-TargetResource: {0} exists and the selected timestamp {1} did not match, leaving
58 existingappearstobeanemptydirectoryRemovingit = {0} appears to be an empty directory. Removing it
59 LastWriteTimemtcheswhatwehaverecordnotreexaminingchecksum = LastWriteTime of {0} matches what we have on record, not re-examining {1}
60 FoundfatdestwheregoingtoplaceoneandhashmatchedContinuing = Found a file at {0} where we were going to place one and hash matched. Continuing
61 FoundfileatdestwhereweweregoingtoplaceoneandhashdidntmatchItwillbeoverwritten = Found a file at $dest where we were going to place one and hash did not match. It will be overwritten
62 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
63 InSetTargetResourcedestexistsandtheselectedtimestamp$ChecksumdidnotmatchForcewasspecifiedwewilloverwrite = "In Set-TargetResource: {0} exists and the selected timestamp {1} did not match. Force was specified, we will overwrite
64 FoundafileatdestandtimestampChecksumdoesnotmatchthesourcebutForcewasnotspecifiedErroring = Found a file at {0} and timestamp {1} does not match the source, but Force was not specified. Erroring
65 FoundadirectoryatdestwhereafileshouldbeRemoving = Found a directory at {0} where a file should be. Removing
66 FounddirectoryatdestwhereafileshouldbeandForcewasnotspecifiedErroring = Found a directory at {0} where a file should be and Force was not specified. Erroring.
67 Writingtofiledest = Writing to file {0}
68 RemovePSDriveonRootdriveRoot = Remove PSDrive on Root {0}
69 Updatingcache = Updating cache
70 FolderDirdoesnotexist = Folder {0} does not exist
71 Examiningdirectorytoseeifitshouldberemoved = Examining {0} to see if it should be removed
72 InSetTargetResourcedestexistsandtheselectedtimestampChecksummatchedwillleaveit = In Set-TargetResource: {0} exists and the selected timestamp {1} matched, will leave it
73 '@
74 }
75
76 Import-LocalizedData LocalizedData -filename ArchiveProvider.psd1
77
78 Import-Module "$PSScriptRoot\..\RunAsHelper.psm1"
79
80 $Debug = $false
81 Function Trace-Message
82 {
83     param([string] $Message)
84     
85     if($Debug)
86     {
87         Write-Verbose $Message
88     }
89 }
90
91 $CacheLocation = "$env:systemroot\system32\Configuration\BuiltinProvCache\MSFT_ArchiveResource"
92
93 Function DSCHashNameToPSHashName
94 {
95     param(
96         [string] $Algorithm
97     )
98     
99     $dscToPsName= @{
100         # Since in-box powershell Get-FileHash cmdlet takes algorithm without hypen, we silently change the names
101         "sha-1" = "SHA1"
102         "sha-256" = "SHA256"
103         "sha-512" = "SHA512"
104     }
105
106     $psName = $dscToPsName[$Algorithm]
107
108     $psName
109 }
110
111
112 Function Get-CacheEntry
113 {
114     param(
115     [string]$path,
116     [string]$destination
117     )
118
119     $key = ($path + $destination).GetHashCode()
120     Trace-Message ($LocalizedData.UsingKeyToRetrieveHashValue -f $Key)
121     $path = Join-Path $CacheLocation $key
122     if(-not (Test-Path $path))
123     {
124         Trace-Message ($LocalizedData.Nocachevaluefound)
125         return @{}
126     }
127     else
128     {
129         # 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)
130         # using a try/catch to work around the issue.
131         try
132         {
133             $tmp = Import-CliXml $path
134             Trace-Message ($LocalizedData.Cachevaluefoundreturning -f $tmp)
135             return $tmp
136         }
137         catch [System.Xml.XmlException] 
138         {
139             Trace-Message ($LocalizedData.CacheCorrupt)
140             return @{}
141         } 
142     }
143 }
144
145 Function Set-CacheEntry
146 {
147     param(
148     [object] $InputObject,
149     [string] $path,
150     [string] $destination
151     )
152     $key = ($path + $destination).GetHashCode()
153     Trace-Message ($LocalizedData.Usingtmpkeytosavehashvalue -f $tmp, $key)
154     $path = Join-Path $CacheLocation $key
155     Trace-Message ($LocalizedData.AbouttocachevalueInputObject -f $InputObject)
156     if(-not (Test-Path $CacheLocation))
157     {
158         mkdir $CacheLocation | Out-Null
159     }
160     
161     Export-CliXml -Path $path -InputObject $InputObject
162 }
163
164 Function Throw-InvalidArgumentException
165 {
166     param(
167         [string] $Message,
168         [string] $ParamName
169     )
170     
171     $exception = new-object System.ArgumentException $Message,$ParamName
172     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,$ParamName,"InvalidArgument",$null
173     throw $errorRecord
174 }
175
176 Function Throw-TerminatingError
177 {
178     param(
179         [string] $Message,
180         [System.Management.Automation.ErrorRecord] $ErrorRecord,
181         [string] $ExceptionType
182     )
183     
184     $exception = new-object "System.InvalidOperationException" $Message,$ErrorRecord.Exception
185     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"MachineStateIncorrect","InvalidOperation",$null
186     throw $errorRecord
187 }
188
189 Function Assert-ValidStandardArgs
190 {
191     param(
192         [string] $Path,
193         [string] $Destination,
194         [boolean] $Validate,
195         [string] $Checksum
196     )
197     
198     #mkdir and Test-Path can each fail with a useful error message
199     #We want to stop our execution if that happens
200     $ErrorActionPreference = "Stop"
201     
202     if(-not (Test-Path -PathType Leaf $Path))
203     {
204         Throw-InvalidArgumentException ($LocalizedData.InvalidSourcePath -f $Path) "Path"
205     }
206     
207     $item = Get-Item -EA Ignore $Destination
208     if($item -and $item.GetType() -eq [System.IO.FileInfo])
209     {
210         Throw-InvalidArgumentException ($LocalizedData.InvalidDestinationDirectory -f $Destination) "Destination"
211     }
212     
213     if($Checksum -and -not $Validate)
214     {
215         Throw-InvalidArgumentException ($LocalizedData.InvalidChecksumArgsMessage -f $Checksum) "Checksum"
216     }
217 }
218
219 Function Compare-FileToEntry
220 {
221     param(
222         [string] $FileName,
223         [object] $Entry,
224         [string] $Algorithm
225     )
226     
227     $existingStream = $null
228     $hash1 = $null
229     try
230     {
231         $existingStream = New-Object System.IO.FileStream $FileName, "Open"
232         $psAlgorithm = DSCHashNameToPSHashName -Algorithm $Algorithm
233         $hash1 = Get-FileHash -InputStream $existingStream -Algorithm $psAlgorithm
234     }
235     catch
236     {
237         Throw-TerminatingError ($LocalizedData.ErrorOpeningExistingFile -f $FileName) $_
238     }
239     finally
240     {
241         if($existingStream -ne $null)
242         {
243             $existingStream.Dispose()
244         }
245     }
246     
247     $hash2 = $Entry.Checksum
248     return ($hash1.Algorithm -eq $hash2.Algorithm) -and ($hash1.Hash -eq $hash2.Hash)
249 }
250
251 Function Get-RelevantChecksumTimestamp
252 {
253     param(
254         [System.IO.FileSystemInfo] $FileSystemObject,
255         [String] $Checksum
256     )
257     
258     if($Checksum.Equals("createddate"))
259     {
260         return $FileSystemObject.CreationTime
261     }
262     else #$Checksum.Equals("modifieddate")
263     {
264         return $FileSystemObject.LastWriteTime
265     }
266 }
267
268 Function Update-Cache
269 {
270     param(
271         [Hashtable] $CacheObject,
272         [System.IO.Compression.ZipArchiveEntry[]] $Entries,
273         [string] $Checksum,
274         [string] $SourceLastWriteTime
275     )
276     
277     Trace-Message ($LocalizedData.InUpdateCache)
278     $cacheEntries = new-object System.Collections.ArrayList
279     foreach($entry in $Entries)
280     {
281         $hash = $null
282         if($Checksum.StartsWith("sha"))
283         {
284             $stream = $null
285             try
286             {
287                 $stream = $entry.Open()
288                 $psAlgorithm = DSCHashNameToPSHashName -Algorithm $Checksum
289                 $hash = Get-FileHash -InputStream $stream -Algorithm $psAlgorithm
290             }
291             finally
292             {
293                 if($stream)
294                 {
295                     $stream.Dispose()
296                 }
297             }
298         }
299         
300         Trace-Message  ($LoalizedData.AddingentryFullNameasacacheentry -f $entry.FullName)
301         $cacheEntries.Add(@{
302             FullName = $entry.FullName
303             LastWriteTime = $entry.LastWriteTime
304             Checksum = $hash
305         }) | Out-Null
306     }
307     
308     Trace-Message ($LocalizedData.UpdatingCacheObject)
309     $CacheObject["SourceLastWriteTime"] = $SourceLastWriteTime
310     $CacheObject["Entries"] = (@() + $cacheEntries)
311     Set-CacheEntry -InputObject $CacheObject -path $Path -destination $Destination
312     Trace-Message ($LocalizedData.Placednewcacheentry)
313 }
314
315 Function Normalize-Checksum
316 {
317     param(
318         [boolean] $Validate,
319         [string] $Checksum
320     )
321     
322     if($Validate)
323     {
324         if(-not $Checksum)
325         {
326             $Checksum = "SHA-256"
327         }
328         
329         $Checksum = $Checksum.ToLower()
330     }
331     
332     Trace-Message ($LocalizedData.NormalizeChecksumreturningChecksum -f $Checksum)
333     return $Checksum
334 }
335
336 # create a psdrive to a net share with a given credential.
337 function Mount-NetworkPath
338 {
339     param ( [string] $path,  [pscredential] $Credential)
340
341     # mount the drive only if not accessible
342     if (Test-Path $path -ErrorAction Ignore)
343     {
344         Trace-Message  ($LocalizedData.PathPathisalreadyaccessiableNomountneeded -f $Path)
345         $psdrive = $null
346     }
347     else
348     {
349         if (-not $path.EndsWith("\"))
350         {
351             $index = $Path.LastIndexOf("\");
352             if (-1 -eq $index)
353             {
354                 Trace-Message ($LocalizedData.Pathpathisnotavalidatenetpath -f $path)
355                 Throw-TerminatingError ($LocalizedData.InvalidNetSourcePath -f $Path)
356             }
357             else
358             {
359                 $path = $path.Substring(0, $index)
360             }
361         }
362
363         $psdriveArgs = @{ Name=([guid]::NewGuid()); PSProvider="FileSystem"; Root=$Path; Scope="Script"; Credential=$Credential }
364         try
365         {
366             Trace-Message ($LocalizedData.createpsdrivewithPathpath -f $path)
367             $psdrive = New-PSDrive @psdriveArgs 
368         }
369         catch
370         {
371             Trace-Message ($LocalizedData.CannotaccessPathPathwithgivenCredential -f $Path)
372             Throw-TerminatingError ($LocalizedData.ErrorOpeningArchiveFile -f $Path) $_
373         }
374     }  
375
376     return $psdrive
377 }
378
379 # The Test-TargetResource cmdlet is used to test the status of item on the destination
380 function Test-TargetResource
381 {
382     param
383     (
384         [ValidateSet("Present", "Absent")]
385         [string] $Ensure = "Present",
386         
387         [parameter(Mandatory = $true)]
388         [ValidateNotNullOrEmpty()]
389         [string] $Path,
390         
391         [parameter(Mandatory = $true)]
392         [ValidateNotNullOrEmpty()]
393         [string] $Destination,
394         
395         [boolean] $Validate = $false,
396         
397         [ValidateSet("", "SHA-1", "SHA-256", "SHA-512", "CreatedDate", "ModifiedDate")]
398         [string] $Checksum,
399
400         [boolean] $Force = $false,
401         
402         [pscredential] $Credential
403     )
404
405     if ($Credential)
406     {
407         $psdrive = Mount-NetworkPath -path $Path -Credential $Credential
408     }
409
410     try
411     {
412         $ErrorActionPreference = "Stop"
413     
414         Trace-Message ($LocalizedData.Abouttovalidatestandardarguments)
415         Assert-ValidStandardArgs $Path $Destination $Validate $Checksum
416         $Checksum = Normalize-Checksum $Validate $Checksum
417         Trace-Message ($LocalizedData.Goingforcacheentries)
418         $result = $true
419         $cacheObj = Get-CacheEntry -path $Path -destination $Destination
420         $sourceLastWriteTime = (Get-Item $Path).LastWriteTime
421         $cacheUpToDate = $cacheObj -and $cacheObj.SourceLastWriteTime -and $cacheObj.SourceLastWriteTime -eq $sourceLastWriteTime
422         $file = $null
423         try
424         {
425             $entries = $null
426             if($cacheUpToDate)
427             {
428                 Trace-Message ($LocalizedData.Thecachewasuptodateusingcachetosatisfyrequests)
429                 $entries = $cacheObj.Entries
430             }
431             else
432             {
433                 Trace-Message ($LocalizedData.Abouttoopenthezipfile)
434                 $entries, $null, $file = Open-ZipFile $Path
435             
436                 Trace-Message ($LocalizedData.Updatingcache)
437                 Update-Cache $cacheObj $entries $Checksum $sourceLastWriteTime
438                 $entries = $cacheObj.Entries
439                 Trace-Message ($LocalizedData.Cacheupdatedwithentries -f $cacheObj.Entries.Length)
440             }
441         
442             foreach($entry in $entries)
443             {
444                 $individualResult = $true
445                 Trace-Message ($LocalizedData.Processing -f $entry.FullName)
446                 $dest = join-path $Destination $entry.FullName
447                 if($dest.EndsWith('\')) #Directory
448                 {
449                     $dest = $dest.TrimEnd('\')
450                     if(-not (Test-Path -PathType Container $dest))
451                     {
452                         Write-Verbose ($LocalizedData.DestMissingOrIncorrectTypeReason -f $dest)
453                         $individualResult = $result = $false
454                     }
455                 }
456                 else
457                 {
458                     $item = Get-Item $dest -EA Ignore
459                     if(-not $item)
460                     {
461                         $individualResult = $result = $false
462                     }
463                     elseif($item.GetType() -ne [System.IO.FileInfo])
464                     {
465                         $individualResult = $result = $false
466                     }
467                 
468                     if(-not $Checksum)
469                     {
470                         Trace-Message ($LocalizedData.InTestTargetResourcedestexistsnotusingchecksumscontinuing -f $dest)
471                         if(-not $individualResult -and $Ensure -eq "Present")
472                         {
473                             Write-Verbose ($LocalizedData.DestMissingOrIncorrectTypeReason -f $dest)
474                         }
475                         elseif($individualResult -and $Ensure -eq "Absent")
476                         {
477                             Write-Verbose ($LocalizedData.DestShouldNotBeThereReason -f $dest)
478                         }
479                     }
480                     else
481                     {
482                         #If the file is there we need to check if it could possibly fail in a different way
483                         #Otherwise we skip all these checks - there's nothing to work with
484                         if($individualResult)
485                         {
486                             $Checksum = $Checksum.ToLower()
487                             if($Checksum.StartsWith("sha"))
488                             {
489                                 if($item.LastWriteTime.Equals($entry.ExistingItemTimestamp))
490                                 {
491                                     Trace-Message ($LocalizedData.Notperformingchecksumthefileondiskhasthesamewritetimeasthelasttimeweverifieditscontents)
492                                 }
493                                 else
494                                 {
495                                     if(-not (Compare-FileToEntry $dest $entry $Checksum))
496                                     {
497                                         $individualResult = $result = $false
498                                     }
499                                     else
500                                     {
501                                         $entry.ExistingItemTimestamp = $item.LastWriteTime
502                                         Trace-Message ($LocalizedData.destexistsandthehashmatcheseven -f $dest)
503                                     }
504                                 }
505                             }
506                             else
507                             {
508                                 $date = Get-RelevantChecksumTimestamp $item $Checksum
509                                 if(-not $date.Equals($entry.LastWriteTime.DateTime))
510                                 {
511                                     $individualResult = $result = $false
512                                 }
513                                 else
514                                 {
515                                     Trace-Message ($LocalizedData.InTestTargetResourcedestexistsandtheselectedtimestampChecksummatched -f $dest, $Checksum)
516                                 }
517                             }
518                         }
519                     
520                         if(-not $individualResult -and $Ensure -eq "Present")
521                         {
522                             Write-Verbose ($LocalizedData.DestHasIncorrectHashvalue -f $dest)
523                         }
524                         elseif($individualResult -and $Ensure -eq "Absent")
525                         {
526                             Write-Verbose ($LocalizedData.DestShouldNotBeThereReason -f $dest)
527                         }
528                     }
529                 }
530             }
531         }
532         finally
533         {
534             if($file)
535             {
536                 $file.Dispose()
537             }
538         }
539     
540         Set-CacheEntry -InputObject $cacheObj -path $Path -destination $Destination
541         $result = $result -eq ("Present" -eq $Ensure)
542     }
543     finally
544     {
545         if($psdrive)
546         {
547             $ErrorActionPreference = "SilentlyContinue"
548             Trace-Message ($LoalizedData.RemovePSDriveonRootpsdrive -f $($psdrive.Root))
549             Remove-PSDrive $psdrive -Force -ErrorAction SilentlyContinue
550         }
551     }
552
553     return $result
554 }
555         
556 Function Ensure-Directory
557 {
558     param([string] $Dir)
559     $item = get-item $Dir -EA SilentlyContinue
560     if(-not $item)
561     {
562         Trace-Message ($LocalizedData.FolderDirdoesnotexist -f $Dir)
563         if($PSCmdlet.ShouldProcess(($LocalizedData.MakeDirectory -f $Dir), $null, $null))
564         {
565             mkdir $Dir | Out-Null
566         }
567     }
568     else
569     {
570         if($item.GetType() -ne [System.IO.DirectoryInfo])
571         {
572             if($Force -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFileAndRecreateAsDirectory -f $Dir), $null, $null))
573             {
574                 Trace-Message ($LocalizedData.RemovingDir -f $Dir)
575                 rm $Dir | Out-Null
576                 mkdir $Dir | Out-Null #Note that we don't do access time translations onto directories since we are emulating the shell's behavior
577             }
578             else
579             {
580                 Throw-TerminatingError ($LocalizedData.ItemExistsButIsWrongType -f $Path)
581             }
582         }
583     }
584 }
585
586 Function Open-ZipFile
587 {
588     param($Path)
589     if (IsNanoServer)
590     {
591         add-type -assemblyname System.IO.Compression
592     }
593     else
594     {
595         add-type -assemblyname System.IO.Compression.FileSystem
596     }
597     $nameHash = @{}
598     try
599     {
600         $fileHandle = ([System.IO.Compression.ZipFile]::OpenRead($Path))
601         $entries = $fileHandle.Entries
602     }    
603     catch
604     {
605         Throw-TerminatingError ($LocalizedData.ErrorOpeningArchiveFile -f $Path) $_
606     }
607     
608     $entries | %{$nameHash[$_.FullName] = $_}
609     return $entries, $nameHash, $fileHandle
610 }
611
612 # The Set-TargetResource cmdlet is used to unpack or remove a zip file to a particular directory
613 function Set-TargetResource 
614 {
615     [CmdletBinding(SupportsShouldProcess=$true)]
616     param
617     (
618         [parameter(Mandatory = $true)]
619         [ValidateNotNullOrEmpty()]
620         [string] $Path,
621         
622         [parameter(Mandatory = $true)]
623         [ValidateNotNullOrEmpty()]
624         [string] $Destination,
625         
626         [ValidateSet("Present", "Absent")]
627         [string] $Ensure = "Present",
628         
629         [boolean] $Validate = $false,
630         
631         [ValidateSet("", "SHA-1", "SHA-256", "SHA-512", "CreatedDate", "ModifiedDate")]
632         [string] $Checksum,
633         
634         [boolean] $Force = $false,
635         
636         [pscredential] $Credential
637     )
638
639     if ($Credential)
640     {
641         $psdrive = Mount-NetworkPath -path $Path -Credential $Credential
642     }
643
644     try
645     {
646         $ErrorActionPreference = "Stop"
647         Assert-ValidStandardArgs $Path $Destination $Validate $Checksum
648         $Checksum = Normalize-Checksum $Validate $Checksum
649         Write-Verbose $LocalizedData.ConfigurationStarted
650         
651         if(-not (Test-Path $Destination))
652         {
653             mkdir $Destination | Out-Null
654         }
655         
656         $cacheObj = Get-CacheEntry -path $Path -destination $Destination
657         $sourceLastWriteTime = (Get-Item $Path).LastWriteTime
658         $cacheUpToDate = $cacheObj -and $cacheObj.SourceLastWriteTime -and $cacheObj.SourceLastWriteTime -eq $sourceLastWriteTime
659         $file = $null
660         $nameToEntry = @{}
661
662         try
663         {
664             # once cache is updated we need to dispose the handle
665             # if not, when the second time $file is updated with a
666             # new handle, the old one not being disposed will lead
667             # to archive resource having a lock on the file
668             if(-not $cacheUpToDate)
669             {
670                 $entries, $nameToEntry, $file = Open-ZipFile $Path
671                 Update-Cache $cacheObj $entries $Checksum $sourceLastWriteTime
672             }
673         }
674         finally
675         {
676             if($file)
677             {
678                 $file.Dispose()
679                 $file = $null
680             }
681
682         }
683
684         try
685         {
686         
687             $entries = $cacheObj.Entries
688             if($Ensure -eq "Absent")
689             {
690                 $directories = new-object system.collections.generic.hashset[string]
691                 foreach($entry in $entries)
692                 {
693                     $isDir = $false
694                     if($entry.FullName.EndsWith("\"))
695                     {
696                         $isDir = $true
697                         $directories.Add((Split-Path -Leaf $entry)) | Out-Null
698                     }
699                 
700                     $parent = $entry.FullName
701                     while(($parent = (Split-Path $parent))) { $directories.Add($parent) | Out-Null }
702                 
703                     if($isDir)
704                     {
705                         #Directory removal is handled as its own pass, see note and code after this loop
706                         continue
707                     }
708                 
709                     $existing = Join-Path $Destination $entry.FullName
710                     $item = Get-Item $existing -EA SilentlyContinue
711                     if(-not $item)
712                     {
713                         continue
714                     }
715                 
716                      #Possible for a folder to have been replaced by a directory of the same name, in which case we must leave it alone
717                     $type = $item.GetType()
718                     if($type -ne [System.IO.FileInfo])
719                     {
720                         continue
721                     }
722                 
723                     if(-not $Checksum -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFile -f $existing), $null, $null))
724                     {
725                         Trace-Message ($LocalizedData.RemovingDir -f $existing)
726                         rm $existing
727                         continue
728                     }
729                 
730                     $Checksum = $Checksum.ToLower()
731                     if($Checksum.StartsWith("sha"))
732                     {
733                         if((Compare-FileToEntry $existing $entry $Checksum) -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFile -f $existing), $null, $null))
734                         {
735                             Trace-Message ($LocalizedData.Hashesofexistingandzipfilesmatchremoving)
736                             rm $existing
737                         }
738                         else
739                         {
740                             Trace-Message ($LocalizedData.HashdidnotmatchfilehasbeenmodifiedsinceitwasextractedLeaving)
741                         }
742                     }
743                     else
744                     {
745                         $date = Get-RelevantChecksumTimestamp $item $Checksum
746                         if($date.Equals($entry.LastWriteTime.DateTime) -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveFile -f $existing), $null, $null))
747                         {
748                             Trace-Message ($LocalizedData.InSetTargetResourceexistsselectedtimestampmatched -f $existing, $Checksum)
749                             rm $existing
750                         }
751                         else
752                         {
753                             Trace-Message ($LocalizedData.InSetTargetResourceexistsdtheselectedtimestampnotmatchg -f $existing, $Checksum)
754                         }
755                     }
756                 }
757             
758                 #Hashset was useful for dropping dupes in an efficient manner, but it can mess with ordering
759                 #Sort according to current culture (directory names can be localized, obviously)
760                 #Reverse so we hit children before parents
761                 $directories = [system.linq.enumerable]::ToList($directories)
762                 $directories.Sort([System.StringComparer]::InvariantCultureIgnoreCase)
763                 $directories.Reverse()
764                 foreach($directory in $directories)
765                 {
766                     Trace-Message ($LocalizedData.Examiningdirectorytoseeifitshouldberemoved -f $directory)
767                     $existing = Join-Path $Destination $directory
768                     $item = Get-Item $existing -EA SilentlyContinue
769                     if($item -and $item.GetType() -eq [System.IO.DirectoryInfo] -and $item.GetFiles().Count -eq 0 -and $item.GetDirectories().Count -eq 0 `
770                          -and $PSCmdlet.ShouldProcess(($LocalizedData.RemoveDirectory -f $existing), $null, $null))
771                     {
772                         Trace-Message ($LocalizedData.existingappearstobeanemptydirectoryRemovingit -f $existing)
773                         rmdir $existing
774                     }
775                 }
776             
777                 Write-Verbose ($LocalizedData.PackageUninstalled -f $Path,$Destination)
778                 Write-Verbose $LocalizedData.ConfigurationFinished
779                 return
780             }
781         
782             Ensure-Directory $Destination
783             foreach($entry in $entries)
784             {
785             $dest = join-path $Destination $entry.FullName
786             if($dest.EndsWith('\')) #Directory
787             {
788                 Ensure-Directory $dest.TrimEnd("\") #Some cmdlets have problems with trailing char
789                 continue
790             }
791             
792             $item = Get-Item $dest -EA SilentlyContinue
793             if($item)
794             {
795                 if($item.GetType() -eq [System.IO.FileInfo])
796                 {
797                     if(-not $Checksum)
798                     {
799                         #It exists. The user didn't specify -Validate, so that's good enough for us
800                         continue
801                     }
802                     
803                     if($Checksum.StartsWith("sha"))
804                     {
805                         if($item.LastWriteTime.Equals($entry.ExistingTimestamp))
806                         {
807                             Trace-Message ($LocalizedData.LastWriteTimemtcheswhatwehaverecordnotreexaminingchecksum -f $dest, $checksum)
808                         }
809                         else
810                         {
811                             $identical = Compare-FileToEntry $dest $entry $Checksum
812                             
813                             if($identical)
814                             {
815                                 Trace-Message ($LocalizedData.FoundfatdestwheregoingtoplaceoneandhashmatchedContinuing -f $dest)
816                                 $entry.ExistingItemTimestamp = $item.LastWriteTime
817                                 continue
818                             }
819                             else
820                             {
821                                 if($Force)
822                                 {
823                                     Trace-Message ($LocalizedData.FoundfileatdestwhereweweregoingtoplaceoneandhashdidntmatchItwillbeoverwritten -f $dest)
824                                 }
825                                 else
826                                 {
827                                     Trace-Message ($LocalizedData.FoundfileatdestwhereweweregoingtoplaceoneanddoesnotmatchthesourcebutForcewasnotspecifiedErroring -f $dest)
828                                     Throw-TerminatingError ($LocalizedData.ItemExistsButIsIncorrect -f $dest)
829                                 }
830                             }
831                         }
832                     }
833                     else
834                     {
835                         $date = Get-RelevantChecksumTimestamp $item $Checksum
836                         if($date.Equals($entry.LastWriteTime.DateTime))
837                         {
838                             Trace-Message ($LocalizedData.InSetTargetResourcedestexistsandtheselectedtimestampChecksummatchedwillleaveit -f $dest, $Checksum)
839                             continue
840                         }
841                         else
842                         {
843                             if($Force)
844                             {
845                                 Trace-Message ($LocalizedData.InSetTargetResourcedestexistsandtheselectedtimestamp -f $dest, $Checksum)
846                             }
847                             else
848                             {
849                                 Trace-Message ($LocalizedData.FoundafileatdestandtimestampChecksumdoesnotmatchthesourcebutForcewasnotspecifiedErroring -f $dest, $Checksum)
850                                 Throw-TerminatingError ($LocalizedData.ItemExistsButIsIncorrect -f $dest)
851                             }
852                         }
853                     }
854                 }
855                 else
856                 {
857                     if($Force)
858                     {
859                         Trace-Message ($LocalizedData.FoundadirectoryatdestwhereafileshouldbeRemoving -f $dest)
860                         if($PSCmdlet.ShouldProcess(($LocalizedData.RemoveDirectory -f $dest), $null, $null))
861                         {
862                             rmdir -Recurse -Force $dest
863                         }
864                     }
865                     else
866                     {
867                         Trace-Message ($LocalizedData.FounddirectoryatdestwhereafileshouldbeandForcewasnotspecifiedErroring -f $dest)
868                         Throw-TerminatingError ($LocalizedData.ItemExistsButIsWrongType -f $dest)
869                     }
870                 }
871             }
872             
873             $parent = Split-Path $dest
874             if(-not (Test-Path $parent) -and $PSCmdlet.ShouldProcess(($LocalizedData.MakeDirectory -f $parent), $null, $null))
875             {
876                 #TODO: This is an edge case we need to revisit. We should be correctly handling wrong file types along
877                 #the directory path if they occur within the archive, but they don't have to. Simple tests demonstrate that
878                 #the Zip format allows you to have the file within a folder without explicitly having an entry for the folder
879                 #This solution will fail in such a case IF anything along the path is of the wrong type (e.g. file in a place
880                 #we expect a directory to be)
881                 mkdir $parent | Out-Null
882             }
883             
884             if($PSCmdlet.ShouldProcess(($LocalizedData.UnzipFile -f $dest), $null, $null))
885             {
886                 #If we get here we can safely blow away anything we find.
887                 $null, $nameToEntry, $file = Open-ZipFile $Path
888                 $stream = $null
889                 $outStream = $null
890                 try
891                 {
892                     Trace-Message ($LocalizedData.Writingtofiledest -f $dest)
893                     $stream = $nameToEntry[$entry.FullName].Open()
894                     $outStream = New-Object System.IO.FileStream $dest, "Create"
895                     $stream.CopyTo($outStream)
896                 }
897                 catch
898                 {
899                     Throw-TerminatingError ($LocalizedData.ErrorCopyingToOutstream -f $dest) $_
900                 }
901                 finally
902                 {
903                     if($stream -ne $null)
904                     {
905                         $stream.Dispose()
906                     }
907                     
908                     if($outStream -ne $null)
909                     {
910                         $outStream.Dispose()
911                     }
912                 }
913                 
914                 $fileInfo = New-Object System.IO.FileInfo $dest
915                 $entry.ExistingItemTimestamp = $fileInfo.LastWriteTime = $fileInfo.LastAccessTime = $fileInfo.CreationTime = $entry.LastWriteTime.DateTime
916             }
917         }
918         }
919         finally
920         {
921             if($file)
922             {
923                 $file.Dispose()
924             }
925         }
926     
927         Set-CacheEntry -InputObject $cacheObj -path $Path -destination $Destination
928         Write-Verbose ($LocalizedData.PackageInstalled -f $Path,$Destination)
929         Write-Verbose $LocalizedData.ConfigurationFinished
930     }
931     finally
932     {
933         if($psdrive)
934         {
935             $ErrorActionPreference = "SilentlyContinue"
936             Trace-Message ($LocalizedData.RemovePSDriveonRootdriveRoot -f ($($psdrive.Root)))
937             Remove-PSDrive $psdrive -Force -ErrorAction SilentlyContinue
938         }
939     }
940 }
941
942 # The Get-TargetResource cmdlet is used to fetch the object
943 function Get-TargetResource
944 {
945     param
946     (
947         [parameter(Mandatory = $true)]
948         [ValidateNotNullOrEmpty()]
949         [string] $Path,
950         
951         [parameter(Mandatory = $true)]
952         [ValidateNotNullOrEmpty()]
953         [string] $Destination,
954         
955         [boolean] $Validate = $false,
956         
957         [ValidateSet("", "SHA-1", "SHA-256", "SHA-512", "CreatedDate", "ModifiedDate")]
958         [string] $Checksum,
959         
960         [pscredential] $Credential
961     )
962     
963     if ($null -eq $Credential)
964     {
965         $PSBoundParameters.Remove("Credential")
966     }
967
968     $exists = Test-TargetResource @PSBoundParameters
969     
970     $stringResult = "Absent"
971     if($exists)
972     {
973         $stringResult = "Present"
974     }
975     
976     @{
977         Ensure = $stringResult;
978         Path = $Path;
979         Destination = $Destination;
980     }
981 }
982
983 Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource