]> insang Git - newton-cn_pos.git/blob
c0b606efb5d698e385d89144a0af8b2206405777
[newton-cn_pos.git] /
1 data LocalizedData
2 {
3     # culture="en-US"
4     # TODO: Support WhatIf
5     ConvertFrom-StringData @'
6 InvalidIdentifyingNumber=The specified IdentifyingNumber ({0}) is not a valid Guid
7 InvalidPath=The specified Path ({0}) is not in a valid format. Valid formats are local paths, UNC, and HTTP
8 InvalidNameOrId=The specified Name ({0}) and IdentifyingNumber ({1}) do not match Name ({2}) and IdentifyingNumber ({3}) in the MSI file
9 NeedsMoreInfo=Either Name or ProductId is required
10 InvalidBinaryType=The specified Path ({0}) does not appear to specify an EXE or MSI file and as such is not supported
11 CouldNotOpenLog=The specified LogPath ({0}) could not be opened
12 CouldNotStartProcess=The process {0} could not be started
13 UnexpectedReturnCode=The return code {0} was not expected. Configuration is likely not correct
14 PathDoesNotExist=The given Path ({0}) could not be found
15 CouldNotOpenDestFile=Could not open the file {0} for writing
16 CouldNotGetHttpStream=Could not get the {0} stream for file {1}
17 ErrorCopyingDataToFile=Encountered error while writing the contents of {0} to {1}
18 PackageConfigurationComplete=Package configuration finished
19 PackageConfigurationStarting=Package configuration starting
20 InstalledPackage=Installed package
21 UninstalledPackage=Uninstalled package
22 NoChangeRequired=Package found in desired state, no action required
23 RemoveExistingLogFile=Remove existing log file
24 CreateLogFile=Create log file
25 MountSharePath=Mount share to get media
26 DownloadHTTPFile=Download the media over HTTP or HTTPS
27 StartingProcessMessage=Starting process {0} with arguments {1}
28 RemoveDownloadedFile=Remove the downloaded file
29 PackageInstalled=Package has been installed
30 PackageUninstalled=Package has been uninstalled
31 MachineRequiresReboot=The machine requires a reboot
32 PackageDoesNotAppearInstalled=The package {0} is not installed
33 PackageAppearsInstalled=The package {0} is installed
34 PostValidationError=Package from {0} was installed, but the specified ProductId and/or Name does not match package details
35 ValidateStandardArgumentsPathwasPath = Validate-StandardArguments, Path was {0}
36 TheurischemewasuriScheme = The uri scheme was {0}
37 ThepathextensionwaspathExt = The path extension was {0}
38 ParsingProductIdasanidentifyingNumber = Parsing {0} as an identifyingNumber
39 ParsedProductIdasidentifyingNumber = Parsed {0} as {1}
40 EnsureisEnsure = Ensure is {0}
41 productisproduct = product {0} found
42 productasbooleanis = product as boolean is {0}
43 Creatingcachelocation = Creating cache location
44 NeedtodownloadfilefromschemedestinationwillbedestName = Need to download file from {0}, destination will be {1}
45 Creatingthedestinationcachefile = Creating the destination cache file
46 Creatingtheschemestream = Creating the {0} stream
47 Settingdefaultcredential = Setting default credential
48 Settingauthenticationlevel = Setting authentication level
49 Ignoringbadcertificates = Ignoring bad certificates
50 Gettingtheschemeresponsestream = Getting the {0} response stream
51 ErrorOutString = Error: {0}
52 Copyingtheschemestreambytestothediskcache = Copying the {0} stream bytes to the disk cache
53 Redirectingpackagepathtocachefilelocation = Redirecting package path to cache file location
54 ThebinaryisanEXE = The binary is an EXE
55 Userhasrequestedloggingneedtoattacheventhandlerstotheprocess = User has requested logging, need to attach event handlers to the process
56 StartingwithstartInfoFileNamestartInfoArguments = Starting {0} with {1}
57 '@
58 }
59
60 Import-LocalizedData LocalizedData -filename PackageProvider.psd1
61
62
63 $Debug = $true
64 Function Trace-Message
65 {
66     param([string] $Message)
67     if($Debug)
68     {
69         Write-Verbose $Message
70     }
71 }
72
73 $CacheLocation = "$env:ProgramData\Microsoft\Windows\PowerShell\Configuration\BuiltinProvCache\MSFT_PackageResource"
74
75 Function Throw-InvalidArgumentException
76 {
77     param(
78         [string] $Message,
79         [string] $ParamName
80     )
81     
82     $exception = new-object System.ArgumentException $Message,$ParamName
83     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,$ParamName,"InvalidArgument",$null
84     throw $errorRecord
85 }
86
87 Function Throw-InvalidNameOrIdException
88 {
89     param(
90         [string] $Message
91     )
92     
93     $exception = new-object System.ArgumentException $Message
94     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"NameOrIdNotInMSI","InvalidArgument",$null
95     throw $errorRecord
96 }
97
98 Function Throw-TerminatingError
99 {
100     param(
101         [string] $Message,
102         [System.Management.Automation.ErrorRecord] $ErrorRecord
103     )
104     
105     $exception = new-object "System.InvalidOperationException" $Message,$ErrorRecord.Exception
106     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"MachineStateIncorrect","InvalidOperation",$null
107     throw $errorRecord
108 }
109
110 Function Validate-StandardArguments
111 {
112     param(
113         $Path,
114         $ProductId,
115         $Name
116     )
117     
118     Trace-Message ($LocalizedData.ValidateStandardArgumentsPathwasPath -f $Path)
119     $uri = $null
120     try
121     {
122         $uri = [uri] $Path
123     }
124     catch
125     {
126         Throw-InvalidArgumentException ($LocalizedData.InvalidPath -f $Path) "Path"
127     }
128     
129     if(-not @("file", "http", "https") -contains $uri.Scheme)
130     {
131         Trace-Message ($Localized.TheurischemewasuriScheme -f $uri.Scheme)
132         Throw-InvalidArgumentException ($LocalizedData.InvalidPath -f $Path) "Path"
133     }
134     
135     $pathExt = [System.IO.Path]::GetExtension($Path)
136     Trace-Message ($LocalizedData.ThepathextensionwaspathExt -f $pathExt)
137     if(-not @(".msi",".exe") -contains $pathExt.ToLower())
138     {
139         Throw-InvalidArgumentException ($LocalizedData.InvalidBinaryType -f $Path) "Path"
140     }
141     
142     $identifyingNumber = $null
143     if(-not $Name -and -not $ProductId)
144     {
145         #It's a tossup here which argument to blame, so just pick ProductId to encourage customers to use the most efficient version
146         Throw-InvalidArgumentException ($LocalizedData.NeedsMoreInfo -f $Path) "ProductId"
147     }
148     elseif($ProductId)
149     {
150         try
151         {
152             Trace-Message ($LocalizedData.ParsingProductIdasanidentifyingNumber -f $ProductId)
153             $identifyingNumber = "{{{0}}}" -f [Guid]::Parse($ProductId).ToString().ToUpper()
154             Trace-Message ($LocalizedData.ParsedProductIdasidentifyingNumber -f $ProductId, $identifyingNumber)
155         }
156         catch
157         {
158             Throw-InvalidArgumentException ($LocalizedData.InvalidIdentifyingNumber -f $ProductId) $ProductId
159         }
160     }
161     
162     return $uri, $identifyingNumber
163 }
164
165 Function Get-ProductEntry
166 {
167     param
168     (
169         [string] $Name,
170         [string] $IdentifyingNumber
171     )
172     
173     $uninstallKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
174     $uninstallKeyWow64 = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
175     
176     if($IdentifyingNumber)
177     {
178         $keyLocation = "$uninstallKey\$identifyingNumber"
179         $item = Get-Item $keyLocation -EA SilentlyContinue
180         if(-not $item)
181         {
182             $keyLocation = "$uninstallKeyWow64\$identifyingNumber"
183             $item = Get-Item $keyLocation -EA SilentlyContinue
184         }
185
186         return $item
187     }
188     
189     foreach($item in (Get-ChildItem -EA Ignore $uninstallKey, $uninstallKeyWow64))
190     {
191         if($Name -eq (Get-LocalizableRegKeyValue $item "DisplayName"))
192         {
193             return $item
194         }
195     }
196     
197     return $null
198 }
199
200 function Test-TargetResource 
201 {
202     param
203     (
204         [ValidateSet("Present", "Absent")]
205         [string] $Ensure = "Present",
206         
207         [parameter(Mandatory = $true)]
208         [AllowEmptyString()]
209         [string] $Name,
210         
211         [parameter(Mandatory = $true)]
212         [ValidateNotNullOrEmpty()]
213         [string] $Path,
214         
215         [parameter(Mandatory = $true)]
216         [AllowEmptyString()]
217         [string] $ProductId,
218         
219         [string] $Arguments,
220         
221         [pscredential] $Credential,
222         
223         [int[]] $ReturnCode,
224         
225         [string] $LogPath
226     )
227     
228     $uri, $identifyingNumber = Validate-StandardArguments $Path $ProductId $Name
229     $product = Get-ProductEntry $Name $identifyingNumber
230     Trace-Message ($LocalizedData.EnsureisEnsure -f $Ensure)
231     if($product)
232     {
233         Trace-Message ($LocalizedData.productisproduct -f $product)
234     }
235     else
236     {
237         Trace-Message "product installation cannot be determined"
238     }
239     Trace-Message ($LocalizedData.productasbooleanis -f [boolean]$product)
240     $res = ($product -ne $null -and $Ensure -eq "Present") -or ($product -eq $null -and $Ensure -eq "Absent")
241
242     if ($product -ne $null)
243     {
244         $name = Get-LocalizableRegKeyValue $product "DisplayName"
245         Write-Verbose ($LocalizedData.PackageAppearsInstalled -f $name)
246     }
247     else
248     {   
249         $displayName = $null
250         if($Name)
251         {
252             $displayName = $Name
253         }
254         else
255         {
256             $displayName = $ProductId
257         }
258     
259         Write-Verbose ($LocalizedData.PackageDoesNotAppearInstalled -f $displayName)
260     }
261
262     return $res
263 }
264
265 function Get-LocalizableRegKeyValue
266 {
267     param(
268         [object] $RegKey,
269         [string] $ValueName
270     )
271     
272     $res = $RegKey.GetValue("{0}_Localized" -f $ValueName)
273     if(-not $res)
274     {
275         $res = $RegKey.GetValue($ValueName)
276     }
277     
278     return $res
279 }
280
281 function Get-TargetResource
282 {
283     param
284     (
285         [parameter(Mandatory = $true)]
286         [AllowEmptyString()]
287         [string] $Name,
288         
289         [parameter(Mandatory = $true)]
290         [ValidateNotNullOrEmpty()]
291         [string] $Path,
292         
293         [parameter(Mandatory = $true)]
294         [AllowEmptyString()]
295         [string] $ProductId
296     )
297     
298     #If the user gave the ProductId then we derive $identifyingNumber
299     $uri, $identifyingNumber = Validate-StandardArguments $Path $ProductId $Name
300     
301     $localMsi = $uri.IsFile -and -not $uri.IsUnc
302     
303     $product = Get-ProductEntry $Name $identifyingNumber
304     
305     if(-not $product)
306     {
307         return @{
308             Ensure = "Absent"
309             Name = $Name
310             ProductId = $identifyingNumber
311             Installed = $false
312         }
313     }
314     
315     #$identifyingNumber can still be null here (e.g. remote MSI with Name specified, local EXE)
316     #If the user gave a ProductId just pass it through, otherwise fill it from the product
317     if(-not $identifyingNumber)
318     {
319         $identifyingNumber = Split-Path -Leaf $product.Name
320     }
321     
322     $date = $product.GetValue("InstallDate")
323     if($date)
324     {
325         try
326         {
327             $date = "{0:d}" -f [DateTime]::ParseExact($date, "yyyyMMdd",[System.Globalization.CultureInfo]::CurrentCulture).Date
328         }
329         catch
330         {
331             $date = $null
332         }
333     }
334     
335     $publisher = Get-LocalizableRegKeyValue $product "Publisher"
336     $size = $product.GetValue("EstimatedSize")
337     if($size)
338     {
339         $size = $size/1024
340     }
341     
342     $version = $product.GetValue("DisplayVersion")
343     $description = $product.GetValue("Comments")
344     $name = Get-LocalizableRegKeyValue $product "DisplayName"
345     return @{
346         Ensure = "Present"
347         Name = $name
348         Path = $Path
349         InstalledOn = $date
350         ProductId = $identifyingNumber
351         Size = $size
352         Installed = $true
353         Version = $version
354         PackageDescription = $description
355         Publisher = $publisher
356     }
357 }
358
359 Function Get-MsiTools
360 {
361     if($script:MsiTools)
362     {
363         return $script:MsiTools
364     }
365     
366     $sig = @'
367         [DllImport("msi.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
368         private static extern UInt32 MsiOpenPackageExW(string szPackagePath, int dwOptions, out IntPtr hProduct);
369
370         [DllImport("msi.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
371         private static extern uint MsiCloseHandle(IntPtr hAny);
372
373         [DllImport("msi.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
374         private static extern uint MsiGetPropertyW(IntPtr hAny, string name, StringBuilder buffer, ref int bufferLength);
375
376         private static string GetPackageProperty(string msi, string property)
377         {
378             IntPtr MsiHandle = IntPtr.Zero;
379             try
380             {
381                 var res = MsiOpenPackageExW(msi, 1, out MsiHandle);
382                 if (res != 0)
383                 {
384                     return null;
385                 }
386
387                 int length = 256;
388                 var buffer = new StringBuilder(length);
389                 res = MsiGetPropertyW(MsiHandle, property, buffer, ref length);
390                 return buffer.ToString();
391             }
392             finally
393             {
394                 if (MsiHandle != IntPtr.Zero)
395                 {
396                     MsiCloseHandle(MsiHandle);
397                 }
398             }
399         }
400         public static string GetProductCode(string msi)
401         {
402             return GetPackageProperty(msi, "ProductCode");
403         }
404
405         public static string GetProductName(string msi)
406         {
407             return GetPackageProperty(msi, "ProductName");
408         }
409 '@
410     $script:MsiTools = Add-Type -PassThru -Namespace Microsoft.Windows.DesiredStateConfiguration.PackageResource `
411         -Name MsiTools -Using System.Text -MemberDefinition $sig
412     return $script:MsiTools
413 }
414
415
416 Function Get-MsiProductEntry
417 {
418     param
419     (
420         [string] $Path
421     )
422
423     if(-not (Test-Path -PathType Leaf $Path) -and ($fileExtension -ne ".msi"))
424     {
425         Throw-TerminatingError ($LocalizedData.PathDoesNotExist -f $Path)
426     }
427     
428     $tools = Get-MsiTools
429
430     $pn = $tools::GetProductName($Path)
431
432     $pc = $tools::GetProductCode($Path)
433
434     return $pn,$pc
435 }
436
437
438 function Set-TargetResource 
439 {
440     [CmdletBinding(SupportsShouldProcess=$true)]
441     param
442     (
443         [ValidateSet("Present", "Absent")]
444         [string] $Ensure = "Present",
445         
446         [parameter(Mandatory = $true)]
447         [AllowEmptyString()]
448         [string] $Name,
449         
450         [parameter(Mandatory = $true)]
451         [ValidateNotNullOrEmpty()]
452         [string] $Path,
453         
454         [parameter(Mandatory = $true)]
455         [AllowEmptyString()]
456         [string] $ProductId,
457         
458         [string] $Arguments,
459         
460         [pscredential] $Credential,
461         
462         [int[]] $ReturnCode,
463         
464         [string] $LogPath
465     )
466     
467     $ErrorActionPreference = "Stop"
468     
469     if (Test-TargetResource -Ensure $Ensure -Name $Name -Path $Path -ProductId $ProductId)
470     {
471         return
472     }
473
474     $uri, $identifyingNumber = Validate-StandardArguments $Path $ProductId $Name
475     $product = Get-ProductEntry $Name $identifyingNumber
476     
477     #Path gets overwritten in the download code path. Retain the user's original Path in case the install succeeded
478     #but the named package wasn't present on the system afterward so we can give a better message
479     $OrigPath = $Path
480     
481     Write-Verbose $LocalizedData.PackageConfigurationStarting
482     if(-not $ReturnCode)
483     {
484         # return code 1641 and 3010 are succeed indication when restart is requested per installation
485         $ReturnCode = @(0, 1641, 3010)
486     }
487     
488     $logStream = $null
489     $psdrive = $null
490     $downloadedFileName = $null
491     try
492     {
493         $fileExtension = [System.IO.Path]::GetExtension($Path).ToLower()
494         if($LogPath)
495         {
496             try
497             {
498                 if($fileExtension -eq ".msi")
499                 {
500                     #We want to pre-verify the path exists and is writable ahead of time
501                     #even in the MSI case, as detecting WHY the MSI log doesn't exist would
502                     #be rather problematic for the user
503                     if((Test-Path $LogPath) -and $PSCmdlet.ShouldProcess($LocalizedData.RemoveExistingLogFile,$null,$null))
504                     {
505                         rm $LogPath
506                     }
507                     
508                     if($PSCmdlet.ShouldProcess($LocalizedData.CreateLogFile, $null, $null))
509                     {
510                         New-Item -Type File $LogPath | Out-Null
511                     }
512                 }
513                 elseif($PSCmdlet.ShouldProcess($LocalizedData.CreateLogFile, $null, $null))
514                 {
515                     $logStream = new-object "System.IO.StreamWriter" $LogPath,$false
516                 }
517             }
518             catch
519             {
520                 Throw-TerminatingError ($LocalizedData.CouldNotOpenLog -f $LogPath) $_
521             }
522         }
523         
524         #Download or mount file as necessary
525         if(-not ($fileExtension -eq ".msi" -and $Ensure -eq "Absent"))
526         {
527             if($uri.IsUnc -and $PSCmdlet.ShouldProcess($LocalizedData.MountSharePath, $null, $null))
528             {
529                 $psdriveArgs = @{Name=([guid]::NewGuid());PSProvider="FileSystem";Root=(Split-Path $uri.LocalPath)}
530                 if($Credential)
531                 {
532                     #We need to optionally include these and then splat the hash otherwise
533                     #we pass a null for Credential which causes the cmdlet to pop a dialog up
534                     $psdriveArgs["Credential"] = $Credential
535                 }
536                 
537                 $psdrive = New-PSDrive @psdriveArgs
538                 $Path = Join-Path $psdrive.Root (Split-Path -Leaf $uri.LocalPath) #Necessary?
539             }
540             elseif(@("http", "https") -contains $uri.Scheme -and $Ensure -eq "Present" -and $PSCmdlet.ShouldProcess($LocalizedData.DownloadHTTPFile, $null, $null))
541             {
542                 $scheme = $uri.Scheme
543                 $outStream = $null
544                 $responseStream = $null
545
546                 try
547                 {
548                     Trace-Message ($LocalizedData.Creatingcachelocation)
549
550                     if(-not (Test-Path -PathType Container $CacheLocation))
551                     {
552                         mkdir $CacheLocation | Out-Null
553                     }
554                 
555                     $destName = Join-Path $CacheLocation (Split-Path -Leaf $uri.LocalPath)
556                 
557                     Trace-Message ($LocalizedData.NeedtodownloadfilefromschemedestinationwillbedestName -f $scheme, $destName)
558
559                     try
560                     {
561                         Trace-Message ($LocalizedData.Creatingthedestinationcachefile)
562                         $outStream = New-Object System.IO.FileStream $destName, "Create"
563                     }
564                     catch
565                     {
566                         #Should never happen since we own the cache directory
567                         Throw-TerminatingError ($LocalizedData.CouldNotOpenDestFile -f $destName) $_
568                     }
569
570                     try
571                     {
572                         Trace-Message ($LocalizedData.Creatingtheschemestream -f $scheme)
573                         $request = [System.Net.WebRequest]::Create($uri)
574                         Trace-Message ($LocalizedData.Settingdefaultcredential)
575                         $request.Credentials = [System.Net.CredentialCache]::DefaultCredentials
576                         if ($scheme -eq "http")
577                         {
578                             Trace-Message ($LocalizedData.Settingauthenticationlevel)
579                             # default value is MutualAuthRequested, which applies to https scheme
580                             $request.AuthenticationLevel = [System.Net.Security.AuthenticationLevel]::None                            
581                         }
582                         if ($scheme -eq "https")
583                         {
584                             Trace-Message ($LocalizedData.Ignoringbadcertificates)
585                             $request.ServerCertificateValidationCallBack = {$true}
586                         }
587                         Trace-Message ($LocalizedData.Gettingtheschemeresponsestream -f $scheme)
588                         $responseStream = (([System.Net.HttpWebRequest]$request).GetResponse()).GetResponseStream()
589                     }
590                     catch
591                     {
592                          Trace-Message ($LocalizedData.ErrorOutString -f ($_ | Out-String))
593                          Throw-TerminatingError ($LocalizedData.CouldNotGetHttpStream -f $scheme, $Path) $_
594                     }
595
596                     try
597                     {
598                         Trace-Message ($LocalizedData.Copyingtheschemestreambytestothediskcache -f $scheme)
599                         $responseStream.CopyTo($outStream)
600                         $responseStream.Flush()
601                         $outStream.Flush()
602                     }
603                     catch
604                     {
605                         Trace-Message ($LocalizedData.ErrorOutString -f ($_ | Out-String))
606                         Throw-TerminatingError ($LocalizedData.ErrorCopyingDataToFile -f $Path,$destName) $_
607                     }
608                 }
609                 finally
610                 {
611                     if($outStream)
612                     {
613                         $outStream.Close()
614                     }
615                     
616                     if($responseStream)
617                     {
618                         $responseStream.Close()
619                     }
620                 }
621                 Trace-Message ($LocalizedData.Redirectingpackagepathtocachefilelocation)
622                 $Path = $downloadedFileName = $destName
623             }
624         }
625         
626         #At this point the Path ought to be valid unless it's an MSI uninstall case
627         if(-not (Test-Path -PathType Leaf $Path) -and -not ($Ensure -eq "Absent" -and $fileExtension -eq ".msi"))
628         {
629             Throw-TerminatingError ($LocalizedData.PathDoesNotExist -f $Path)
630         }
631         
632         $startInfo = New-Object System.Diagnostics.ProcessStartInfo
633         $startInfo.UseShellExecute = $false #Necessary for I/O redirection and just generally a good idea
634         $process = New-Object System.Diagnostics.Process
635         $process.StartInfo = $startInfo
636         $errLogPath = $LogPath + ".err" #Concept only, will never touch disk
637         
638         if($fileExtension -eq ".msi")
639         {
640             $startInfo.FileName = "$env:windir\system32\msiexec.exe"
641             if($Ensure -eq "Present")
642             {
643                 # check if Msi package contains the ProductName and Code specified
644
645                 $pName,$pCode = Get-MsiProductEntry -Path $Path
646
647                 if (
648                     ( (-not [String]::IsNullOrEmpty($Name)) -and ($pName -ne $Name))  `
649                 -or ( (-not [String]::IsNullOrEmpty($identifyingNumber)) -and ($identifyingNumber -ne $pCode))
650                 )
651                 {
652                     Throw-InvalidNameOrIdException ($LocalizedData.InvalidNameOrId -f $Name,$identifyingNumber,$pName,$pCode)
653                 }
654
655                 $startInfo.Arguments = '/i "{0}"' -f $Path
656             }
657             else
658             {
659                 $id = Split-Path -Leaf $product.Name #We may have used the Name earlier, now we need the actual ID
660                 $startInfo.Arguments = ("/x{0}" -f $id)
661             }
662             
663             if($LogPath)
664             {
665                 $startInfo.Arguments += ' /log "{0}"' -f $LogPath
666             }
667             
668             $startInfo.Arguments += " /quiet"
669             
670             if($Arguments)
671             {
672                 $startInfo.Arguments += " " + $Arguments
673             }
674         }
675         else #EXE
676         {
677             Trace-Message ($LocalizedData.ThebinaryisanEXE)
678             if($Ensure -eq "Present")
679             {
680                 $startInfo.FileName = $Path
681                 $startInfo.Arguments = $Arguments
682                 if($LogPath)
683                 {
684                     Trace-Message ($LocalizedData.Userhasrequestedloggingneedtoattacheventhandlerstotheprocess)
685                     $startInfo.RedirectStandardError = $true
686                     $startInfo.RedirectStandardOutput = $true
687                     Register-ObjectEvent -InputObject $process -EventName "OutputDataReceived" -SourceIdentifier $LogPath
688                     Register-ObjectEvent -InputObject $process -EventName "ErrorDataReceived" -SourceIdentifier $errLogPath
689                 }
690             } else {
691                 # Absent case
692                 $startInfo.FileName = "$env:windir\system32\msiexec.exe"
693                 $id = Split-Path -Leaf $product.Name
694                 $startInfo.Arguments = ("/x{0} /quiet" -f $id)
695                 # Never let the msiexec to restart automatically, DSC should handle reboot requests
696                 $startInfo.Arguments += ' /norestart'
697                 if($LogPath)
698                 {
699                     $startInfo.Arguments += ' /log "{0}"' -f $LogPath
700                 }
701                 
702                 if($Arguments)
703                 {
704                     $startInfo.Arguments += " " + $Arguments
705                 }
706             }
707         }
708         
709         Trace-Message ($LocalizedData.StartingwithstartInfoFileNamestartInfoArguments -f $startInfo.FileName, $startInfo.Arguments)
710         
711         if($PSCmdlet.ShouldProcess(($LocalizedData.StartingProcessMessage -f $startInfo.FileName, $startInfo.Arguments), $null, $null))
712         {
713             try
714             {
715                 $exitcode = 0
716                 $process.Start() | Out-Null
717                 if($logStream) #Identical to $fileExtension -eq ".exe" -and $logPath
718                  {
719                      $process.BeginOutputReadLine();
720                      $process.BeginErrorReadLine();
721                  }
722           
723                  $process.WaitForExit()
724
725                  if($process)
726                  {
727                     $exitCode = $process.ExitCode
728                  }
729
730             }
731             catch
732             {
733                 Throw-TerminatingError ($LocalizedData.CouldNotStartProcess -f $Path) $_
734             }
735
736             
737             if($logStream)
738             {
739                 #We have to re-mux these since they appear to us as different streams
740                 #The underlying Win32 APIs prevent this problem, as would constructing a script
741                 #on the fly and executing it, but the former is highly problematic from PowerShell
742                 #and the latter doesn't let us get the return code for UI-based EXEs
743                 $outputEvents = Get-Event -SourceIdentifier $LogPath
744                 $errorEvents = Get-Event -SourceIdentifier $errLogPath
745                 $masterEvents = @() + $outputEvents + $errorEvents
746                 $masterEvents = $masterEvents | Sort-Object -Property TimeGenerated
747                 
748                 foreach($event in $masterEvents)
749                 {
750                     $logStream.Write($event.SourceEventArgs.Data);
751                 }
752                 
753                 Remove-Event -SourceIdentifier $LogPath
754                 Remove-Event -SourceIdentifier $errLogPath
755             }
756             
757             if(-not ($ReturnCode -contains $exitCode))
758             {
759                 Throw-TerminatingError ($LocalizedData.UnexpectedReturnCode -f $exitCode.ToString())
760             }
761         }
762     }
763     finally
764     {
765         if($psdrive)
766         {
767             Remove-PSDrive -Force $psdrive
768         }
769         
770         if($logStream)
771         {
772             $logStream.Dispose()
773         }
774     }
775     
776     if($downloadedFileName -and $PSCmdlet.ShouldProcess($LocalizedData.RemoveDownloadedFile, $null, $null))
777     {
778         #This is deliberately not in the Finally block. We want to leave the downloaded file on disk
779         #in the error case as a debugging aid for the user
780         rm $downloadedFileName
781     }
782     
783     $operationString = $LocalizedData.PackageUninstalled
784     if($Ensure -eq "Present")
785     {
786         $operationString = $LocalizedData.PackageInstalled
787     }
788     
789     # Check if reboot is required, if so notify CA. The MSFT_ServerManagerTasks provider is missing on client SKUs (Worked on both Server and Client Skus as in windows 10)
790     $featureData = invoke-wmimethod -EA Ignore -Name GetServerFeature -namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks
791     $regData = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" "PendingFileRenameOperations" -EA Ignore
792     if(($featureData -and $featureData.RequiresReboot) -or $regData -or $exitcode -eq 3010 -or $exitcode -eq 1641)
793     {
794         Write-Verbose $LocalizedData.MachineRequiresReboot
795         $global:DSCMachineStatus = 1
796     }
797     
798     if($Ensure -eq "Present")
799     {
800         $productEntry = Get-ProductEntry $Name $identifyingNumber
801         if(-not $productEntry)
802         {
803             Throw-TerminatingError ($LocalizedData.PostValidationError -f $OrigPath)
804         }
805     }
806     
807     Write-Verbose $operationString
808     Write-Verbose $LocalizedData.PackageConfigurationComplete
809 }
810
811 Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource