29 August 2016

PowerShell: Configuring Power Settings

This is another part of the Windows 10 project I am working on automating. This script will set the Windows 10 power settings. I created this, with the help of Sapien's PowerShell Studio that helped make this script much more robust, so that it can be executed during the image to configure the settings there. This allows me to take the settings out of GPO and put them on the local machine to help speed up the logon process. The users at the firm I work at do not have local administrator privileges, so this works great.

The advantage to using this script for configuring power settings, over using powercfg.exe directly, is that the script will verify the setting took place. It goes back and checks the new value to make sure it coincides with the desired value. If it was not successful, the script will return an error code 5, which I arbitrarily chose. This allows you to use the script in a build and it will report the error back to alert you at the end of the build if the power setting was unsuccessful.

The script can set individual power settings per the command line, you can hardcode the settings into the script (I commented out an example), or you can import a power scheme. I also included the feature to generate a detailed and formatted report of all power settings on a machine. The report not only displays to the screen, but it also generates a file named PowerSchemeReport.txt in the same directory as the script. I have included command line examples in the comments of the script.

Here is an example of the script generating a report:


Here is an example of setting the Monitor Timeout while plugged in to 120 minutes:


Here is an example of setting the power scheme from high performance to balanced:


Here is an example of the script importing a power scheme configuration file and setting it as the default:




You can download the file from here.


Set-PowerScheme.ps1

1:  <#  
2:       .SYNOPSIS  
3:            Set the Power Options  
4:         
5:       .DESCRIPTION  
6:            This script will set the preferred plan. It can also customize specific settings within a plan. The is an option to use the script for generating a report on the currently selected plan, along with all of the plan settings that is written both to the screen and to a log file. The script will exit with an error code 5 if any power setting failed. This allows for an error flag when used during a build.  
7:         
8:       .PARAMETER Balanced  
9:            Selects the balanced plan  
10:         
11:       .PARAMETER ConsoleTitle  
12:            Name for the PowerShell Console Title  
13:         
14:       .PARAMETER Custom  
15:            Enter a name to create a custom Power Plan  
16:         
17:       .PARAMETER HighPerformance  
18:            Selects the High Performance Plan  
19:         
20:       .PARAMETER ImportPowerSchemeFile  
21:            Import a power scheme file  
22:         
23:       .PARAMETER PowerSaver  
24:            Selects the Power Saver Plan  
25:         
26:       .PARAMETER PowerSchemeName  
27:            Name to use when renaming an imported scheme  
28:         
29:       .PARAMETER Report  
30:            Select this switch to generate a report of the currently selected plan  
31:         
32:       .PARAMETER SetPowerSchemeSetting  
33:            Set individual power scheme setting  
34:         
35:       .PARAMETER SetPowerSchemeSettingValue  
36:            Value associated with the Power Scheme Setting  
37:         
38:       .PARAMETER SetImportedPowerSchemeDefault  
39:            This is used in conjunction with the ImportPowerSchemeFile parameter. This tells the script to set the imported power scheme as the default.  
40:         
41:       .EXAMPLE  
42:            Set Power Settings to Balanced  
43:            powershell.exe -executionpolicy bypass -file Set-PowerScheme.ps1 -Balanced  
44:              
45:            Set Power Settings to High Performance  
46:            powershell.exe -executionpolicy bypass -file Set-PowerScheme.ps1 -HighPerformance  
47:              
48:            Set Power Settings to Power Saver  
49:            powershell.exe -executionpolicy bypass -file Set-PowerScheme.ps1 -PowerSaver  
50:              
51:            Generate a report named PowerSchemeReport.txt that resides in the same directory as this script. It contains a list of all power settings.  
52:            powershell.exe -executionpolicy bypass -file Set-PowerScheme.ps1 -Report  
53:              
54:            Set individual power scheme setting  
55:            powershell.exe -executionpolicy bypass -file Set-PowerScheme.ps1 -SetPowerSchemeSetting -MonitorTimeoutAC 120  
56:              
57:            Import power scheme file that resides in the same directory as this script and renames the scheme to the name defined under PowerSchemeName  
58:            powershell.exe -executionpolicy bypass -file Set-PowerScheme.ps1 -ImportPowerSchemeFile "CustomScheme.cfg" -PowerSchemeName "Custom"  
59:         
60:       .NOTES  
61:            ===========================================================================  
62:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127  
63:            Created on:       8/16/2016 10:13 AM  
64:            Created by:       Mick Pletcher  
65:            Organization:  
66:            Filename:        Set-PowerScheme.ps1  
67:            ===========================================================================  
68:  #>  
69:  [CmdletBinding()]  
70:  param  
71:  (  
72:       [switch]  
73:       $Balanced,  
74:       [string]  
75:       $ConsoleTitle = 'PowerScheme',  
76:       [string]  
77:       $Custom,  
78:       [switch]  
79:       $HighPerformance,  
80:       [string]  
81:       $ImportPowerSchemeFile,  
82:       [switch]  
83:       $PowerSaver,  
84:       [string]  
85:       $PowerSchemeName,  
86:       [switch]  
87:       $Report,  
88:       [ValidateSet('MonitorTimeoutAC', 'MonitorTimeoutDC', 'DiskTimeoutAC', 'DiskTimeoutDC', 'StandbyTimeoutAC', 'StandbyTimeoutDC', 'HibernateTimeoutAC', 'HibernateTimeoutDC')][string]  
89:       $SetPowerSchemeSetting,  
90:       [string]  
91:       $SetPowerSchemeSettingValue,  
92:       [switch]  
93:       $SetImportedPowerSchemeDefault  
94:  )  
95:    
96:  function Get-PowerScheme {  
97:  <#  
98:       .SYNOPSIS  
99:            Get the currently active PowerScheme  
100:         
101:       .DESCRIPTION  
102:            This will query the current power scheme and return the GUID and user friendly name  
103:         
104:       .EXAMPLE  
105:            PS C:\> Get-PowerScheme  
106:         
107:       .NOTES  
108:            Additional information about the function.  
109:  #>  
110:         
111:       [CmdletBinding()][OutputType([object])]  
112:       param ()  
113:         
114:       #Get the currently active power scheme  
115:       $Query = powercfg.exe /getactivescheme  
116:       #Get the alias name of the active power scheme  
117:       $ActiveSchemeName = ($Query.Split("()").Trim())[1]  
118:       #Get the GUID of the active power scheme  
119:       $ActiveSchemeGUID = ($Query.Split(":(").Trim())[1]  
120:       $Query = powercfg.exe /query $ActiveSchemeGUID  
121:       $GUIDAlias = ($Query | where { $_.Contains("GUID Alias:") }).Split(":")[1].Trim()  
122:       $Scheme = New-Object -TypeName PSObject  
123:       $Scheme | Add-Member -Type NoteProperty -Name PowerScheme -Value $ActiveSchemeName  
124:       $Scheme | Add-Member -Type NoteProperty -Name GUIDAlias -Value $GUIDAlias  
125:       $Scheme | Add-Member -Type NoteProperty -Name GUID -Value $ActiveSchemeGUID  
126:       Return $Scheme  
127:  }  
128:    
129:  function Get-PowerSchemeSubGroupSettings {  
130:  <#  
131:       .SYNOPSIS  
132:            Get the Power Scheme SubGroup Settings  
133:         
134:       .DESCRIPTION  
135:            Retrieve all Settings and values within a subgroup  
136:         
137:       .PARAMETER Subgroup  
138:            Name and GUID of desired subgroup  
139:         
140:       .PARAMETER ActivePowerScheme  
141:            GUID and name of the active ActivePowerScheme  
142:         
143:       .EXAMPLE  
144:            PS C:\> Get-PowerSchemeSubGroupSettings -Subgroup $value1  
145:         
146:       .NOTES  
147:            Additional information about the function.  
148:  #>  
149:         
150:       [CmdletBinding()]  
151:       param  
152:       (  
153:            [ValidateNotNullOrEmpty()]$Subgroup,  
154:            [ValidateNotNullOrEmpty()][object]  
155:            $ActivePowerScheme  
156:       )  
157:         
158:       $Query = powercfg.exe /query $ActivePowerScheme.GUID $Subgroup.GUID  
159:       $Query = $Query | where { ((!($_.Contains($ActivePowerScheme.GUID))) -and (!($_.Contains($ActivePowerScheme.GUIDAlias)))) }  
160:       $Settings = @()  
161:       For ($i = 0; $i -lt $Query.Length; $i++) {  
162:            If ($Query[$i] -like "*Power Setting GUID:*") {  
163:                 $Setting = New-Object System.Object  
164:                 #Get the friendly name of the Power Setting  
165:                 $SettingName = $Query[$i].Split("()").Trim()  
166:                 $SettingName = $SettingName[1]  
167:                 #Get the alias of the power setting  
168:                 If ($Query[$i + 1] -like "*GUID Alias:*") {  
169:                      $SettingAlias = $Query[$i + 1].Split(":").Trim()  
170:                      $SettingAlias = $SettingAlias[1]  
171:                 } else {  
172:                      $SettingAlias = $null  
173:                 }  
174:                 #Get the GUID of the power setting  
175:                 $SettingGUID = $Query[$i].Split(":(").Trim()  
176:                 $SettingGUID = $SettingGUID[1]  
177:                 #Get the AC and DC power settings  
178:                 $j = $i  
179:                 Do {  
180:                      $j++  
181:                 }  
182:                 while ($Query[$j] -notlike "*Current AC Power Setting*")  
183:                 $SettingAC = $Query[$j].Split(":").Trim()  
184:                 $SettingAC = [Convert]::ToInt32($SettingAC[1], 16)  
185:                 $SettingDC = $Query[$j + 1].Split(":").Trim()  
186:                 $SettingDC = [Convert]::ToInt32($SettingDC[1], 16)  
187:                 $Setting | Add-Member -Type NoteProperty -Name Subgroup -Value $Subgroup.Subgroup  
188:                 $Setting | Add-Member -Type NoteProperty -Name Name -Value $SettingName  
189:                 $Setting | Add-Member -Type NoteProperty -Name Alias -Value $SettingAlias  
190:                 $Setting | Add-Member -Type NoteProperty -Name GUID -Value $SettingGUID  
191:                 $Setting | Add-Member -Type NoteProperty -Name AC -Value $SettingAC  
192:                 $Setting | Add-Member -Type NoteProperty -Name DC -Value $SettingDC  
193:                 $Settings += $Setting  
194:            }  
195:       }  
196:       Return $Settings  
197:  }  
198:    
199:  function Get-RelativePath {  
200:  <#  
201:       .SYNOPSIS  
202:            Get the relative path  
203:         
204:       .DESCRIPTION  
205:            Returns the location of the currently running PowerShell script  
206:         
207:       .NOTES  
208:            Additional information about the function.  
209:  #>  
210:         
211:       [CmdletBinding()][OutputType([string])]  
212:       param ()  
213:         
214:       $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
215:       Return $Path  
216:  }  
217:    
218:  function Get-SubGroupsList {  
219:  <#  
220:       .SYNOPSIS  
221:            Generate a list of subgroups  
222:         
223:       .DESCRIPTION  
224:            This will generate a list of the subgroups within the designated power scheme  
225:         
226:       .PARAMETER ActivePowerScheme  
227:            GUID and name of the active ActivePowerScheme  
228:         
229:       .EXAMPLE  
230:            PS C:\> Get-SubGroupsList  
231:         
232:       .NOTES  
233:            Additional information about the function.  
234:  #>  
235:         
236:       [CmdletBinding()][OutputType([object])]  
237:       param  
238:       (  
239:            [ValidateNotNullOrEmpty()][object]  
240:            $ActivePowerScheme  
241:       )  
242:         
243:       #Get all settings for the active power scheme  
244:       $Query = powercfg.exe /query $ActivePowerScheme.GUID  
245:       #Get a list of the subgroups  
246:       $Subgroups = @()  
247:       for ($i = 0; $i -lt $Query.Length; $i++) {  
248:            If (($Query[$i] -like "*Subgroup GUID:*") -and ($Query[$i + 1] -notlike "*Subgroup GUID:*")) {  
249:                 $Subgroup = New-Object System.Object  
250:                 $SubgroupName = $Query[$i].Split("()").Trim()  
251:                 $SubgroupName = $SubgroupName[1]  
252:                 If ($Query[$i + 1] -like "*GUID Alias:*") {  
253:                      $SubgroupAlias = $Query[$i + 1].Split(":").Trim()  
254:                      $SubgroupAlias = $SubgroupAlias[1]  
255:                 } else {  
256:                      $SubgroupAlias = $null  
257:                 }  
258:                 $SubgroupGUID = $Query[$i].Split(":(").Trim()  
259:                 $SubgroupGUID = $SubgroupGUID[1]  
260:                 $Subgroup | Add-Member -Type NoteProperty -Name Subgroup -Value $SubgroupName  
261:                 $Subgroup | Add-Member -Type NoteProperty -Name Alias -Value $SubgroupAlias  
262:                 $Subgroup | Add-Member -Type NoteProperty -Name GUID -Value $SubgroupGUID  
263:                 $Subgroups += $Subgroup  
264:            }  
265:       }  
266:       Return $Subgroups  
267:  }  
268:    
269:  function Import-PowerScheme {  
270:  <#  
271:       .SYNOPSIS  
272:            Import a Power Scheme  
273:         
274:       .DESCRIPTION  
275:            Imports a power scheme configuration file  
276:         
277:       .PARAMETER File  
278:            Name of the configuration file. This must reside in the same directory as this script.  
279:         
280:       .PARAMETER PowerSchemeName  
281:            Desired name for the imported power scheme  
282:         
283:       .PARAMETER SetActive  
284:            Set the imported scheme to active  
285:         
286:       .EXAMPLE  
287:            PS C:\> Import-PowerScheme -File 'Value1'  
288:         
289:       .NOTES  
290:            Additional information about the function.  
291:  #>  
292:         
293:       [CmdletBinding()][OutputType([boolean])]  
294:       param  
295:       (  
296:            [ValidateNotNullOrEmpty()][string]  
297:            $File,  
298:            [ValidateNotNullOrEmpty()][string]  
299:            $PowerSchemeName,  
300:            [switch]  
301:            $SetActive  
302:       )  
303:         
304:       $RelativePath = Get-RelativePath  
305:       $File = $RelativePath + $File  
306:       #Get list of all power schemes  
307:       $OldPowerSchemes = powercfg.exe /l  
308:       #Filter out all data except for the GUID  
309:       $OldPowerSchemes = $OldPowerSchemes | where { $_ -like "*Power Scheme GUID*" } | ForEach-Object { $_ -replace "Power Scheme GUID: ", "" } | ForEach-Object { ($_.split("?("))[0] }  
310:       Write-Host "Importing Power Scheme....." -NoNewline  
311:       #Import Power Scheme  
312:       $Output = powercfg.exe -import $File  
313:       #Get list of all power schemes  
314:       $NewPowerSchemes = powercfg.exe /l  
315:       #Filter out all data except for the GUID  
316:       $NewScheme = $NewPowerSchemes | where { $_ -like "*Power Scheme GUID*" } | ForEach-Object { $_ -replace "Power Scheme GUID: ", "" } | ForEach-Object { ($_.split("?("))[0] } | where { $OldPowerSchemes -notcontains $_ }  
317:       If ($NewScheme -ne $null) {  
318:            Write-Host "Success" -ForegroundColor Yellow  
319:            $Error = $false  
320:       } else {  
321:            Write-Host "Failed" -ForegroundColor Red  
322:            $Error = $true  
323:       }  
324:       #Rename imported power scheme  
325:       Write-Host "Renaming imported power scheme to"$PowerSchemeName"....." -NoNewline  
326:       $Switches = "/changename" + [char]32 + $NewScheme.Trim() + [char]32 + [char]34 + $PowerSchemeName + [char]34  
327:       $ErrCode = (Start-Process -FilePath "powercfg.exe" -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode  
328:       $NewPowerSchemes = powercfg.exe /l  
329:       If ($ErrCode -eq 0) {  
330:            $Test = $NewPowerSchemes | where { $_ -like ("*" + $PowerSchemeName + "*") }  
331:            If ($Test -ne $null) {  
332:                 Write-Host "Success" -ForegroundColor Yellow  
333:                 $Error = $false  
334:            } else {  
335:                 Write-Host "Failed" -ForegroundColor Red  
336:                 $Error = $true  
337:                 Return $Error  
338:            }  
339:       }  
340:       Write-Host "Setting"$PowerSchemeName" to default....." -NoNewline  
341:       $Switches = "-setactive " + $NewScheme.Trim()  
342:       $ErrCode = (Start-Process -FilePath "powercfg.exe" -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode  
343:       $Query = powercfg.exe /getactivescheme  
344:       #Get the alias name of the active power scheme  
345:       $ActiveSchemeName = (powercfg.exe /getactivescheme).Split("()").Trim()[1]  
346:       If ($ActiveSchemeName -eq $PowerSchemeName) {  
347:            Write-Host "Success" -ForegroundColor Yellow  
348:            $Error = $false  
349:       } else {  
350:            Write-Host "Failed" -ForegroundColor Red  
351:            $Error = $true  
352:       }  
353:       Return $Error  
354:  }  
355:    
356:  function Publish-Report {  
357:  <#  
358:       .SYNOPSIS  
359:            Publish a Power Scheme Report  
360:         
361:       .DESCRIPTION  
362:            This will publish a report of the currently active power scheme, a list of the power scheme subgroups, and a list of all subgroup settings.  
363:         
364:       .EXAMPLE  
365:            PS C:\> Publish-Report  
366:         
367:       .NOTES  
368:            Additional information about the function.  
369:  #>  
370:         
371:       [CmdletBinding()]  
372:       param ()  
373:         
374:       #Get the relative path this script is being executed from  
375:       $RelativePath = Get-RelativePath  
376:       #Get the currently enabled power scheme data  
377:       $ActivePowerScheme = Get-PowerScheme  
378:       #Get a list of all available subgroups  
379:       $PowerSchemeSubGroups = Get-SubGroupsList -ActivePowerScheme $ActivePowerScheme  
380:       #Get a list of all settings under each subgroup  
381:       $PowerSchemeSettings = @()  
382:       for ($i = 0; $i -lt $PowerSchemeSubGroups.Length; $i++) {  
383:            $PowerSchemeSubGroupSettings = Get-PowerSchemeSubGroupSettings -ActivePowerScheme $ActivePowerScheme -Subgroup $PowerSchemeSubGroups[$i]  
384:            $PowerSchemeSettings += $PowerSchemeSubGroupSettings  
385:       }  
386:       #Define the Report text file to write to  
387:       $ReportFile = $RelativePath + "PowerSchemeReport.txt"  
388:       #Remove old report if it exists  
389:       If ((Test-Path $ReportFile) -eq $true) {  
390:            Remove-Item -Path $ReportFile -Force  
391:       }  
392:       #Generate Header for Power Scheme Report  
393:       $Header = "ACTIVE POWER SCHEME REPORT"  
394:       $Header | Tee-Object -FilePath $ReportFile -Append  
395:       $Header = "--------------------------------------------------------------------------------"  
396:       $Header | Tee-Object -FilePath $ReportFile -Append  
397:       #Get Active Power Scheme report  
398:       $Output = $ActivePowerScheme | Format-Table  
399:       #Write output to report screen and file  
400:       $Output | Tee-Object -FilePath $ReportFile -Append  
401:       #Generate Header for power scheme subgroups report  
402:       $Header = "POWER SCHEME SUBGROUPS REPORT"  
403:       $Header | Tee-Object -FilePath $ReportFile -Append  
404:       $Header = "--------------------------------------------------------------------------------"  
405:       $Header | Tee-Object -FilePath $ReportFile -Append  
406:       $Output = $PowerSchemeSubgroups | Format-Table  
407:       #Write output to report screen and file  
408:       $Output | Tee-Object -FilePath $ReportFile -Append  
409:       #Generate Header for power scheme subgroup settings report  
410:       $Header = "POWER SCHEME SUBGROUP SETTINGS REPORT"  
411:       $Header | Tee-Object -FilePath $ReportFile -Append  
412:       $Header = "--------------------------------------------------------------------------------"  
413:       $Header | Tee-Object -FilePath $ReportFile -Append  
414:       $Output = $PowerSchemeSettings | Format-Table  
415:       #Write output to report screen and file  
416:       $Output | Tee-Object -FilePath $ReportFile -Append  
417:  }  
418:    
419:  function Set-ConsoleTitle {  
420:  <#  
421:       .SYNOPSIS  
422:            Console Title  
423:         
424:       .DESCRIPTION  
425:            Sets the title of the PowerShell Console  
426:         
427:       .PARAMETER Title  
428:            Title of the PowerShell Console  
429:         
430:       .NOTES  
431:            Additional information about the function.  
432:  #>  
433:         
434:       [CmdletBinding()]  
435:       param  
436:       (  
437:            [Parameter(Mandatory = $true)][String]  
438:            $Title  
439:       )  
440:         
441:       $host.ui.RawUI.WindowTitle = $Title  
442:  }  
443:    
444:  function Set-PowerScheme {  
445:  <#  
446:       .SYNOPSIS  
447:            Set the power scheme to the specified scheme  
448:         
449:       .DESCRIPTION  
450:            Sets the power scheme to the specified scheme  
451:         
452:       .PARAMETER PowerScheme  
453:            Friendly power scheme name  
454:         
455:       .PARAMETER CustomPowerScheme  
456:            Create a custom power scheme  
457:         
458:       .EXAMPLE  
459:            PS C:\> Set-PowerScheme -PowerScheme 'Value1'  
460:         
461:       .NOTES  
462:            Additional information about the function.  
463:  #>  
464:         
465:       [CmdletBinding()][OutputType([boolean])]  
466:       param  
467:       (  
468:            [ValidateSet('Balanced', 'High Performance', 'Power Saver')][string]  
469:            $PowerScheme,  
470:            [string]  
471:            $CustomPowerScheme  
472:       )  
473:         
474:       #Get list of existing power schemes  
475:       $PowerSchemes = powercfg.exe /l  
476:       If ($PowerScheme -ne $null) {  
477:            #Filter out all schemes except for $PowerScheme and return the GUID  
478:            $PowerSchemes = ($PowerSchemes | where { $_ -like "*" + $PowerScheme + "*" }).Split(":(").Trim()[1]  
479:            #Set power scheme  
480:            $ActivePowerScheme = Get-PowerScheme  
481:            $ActivePowerScheme.PowerScheme  
482:            Write-Host "Setting Power Scheme from"$ActivePowerScheme.PowerScheme"to"$PowerScheme"....." -NoNewline  
483:            $Output = powercfg.exe -setactive $PowerSchemes  
484:            $ActivePowerScheme = Get-PowerScheme  
485:            If ($PowerScheme -eq $ActivePowerScheme.PowerScheme) {  
486:                 Write-Host "Success" -ForegroundColor Yellow  
487:                 Return $false  
488:            } else {  
489:                 Write-Host "Failed" -ForegroundColor Red  
490:                 Return $true  
491:            }  
492:       }  
493:  }  
494:    
495:  function Set-PowerSchemeSettings {  
496:  <#  
497:       .SYNOPSIS  
498:            Modify current power scheme  
499:         
500:       .DESCRIPTION  
501:            This will modify settings of the currently active power scheme.  
502:         
503:       .PARAMETER MonitorTimeoutAC  
504:            Modify the time until the screensaver turns on while plugged into AC outlet  
505:         
506:       .PARAMETER MonitorTimeoutDC  
507:            Modify the time until the screensaver turns on while on battery power  
508:         
509:       .PARAMETER DiskTimeoutAC  
510:            Time that windows will wait for a hard disk to respond to a command while plugged into AC outlet  
511:         
512:       .PARAMETER DiskTimeoutDC  
513:            Time that windows will wait for a hard disk to respond to a command while on battery power  
514:         
515:       .PARAMETER StandbyTimeoutAC  
516:            Amount of time before a computer is put on standby while plugged into AC outlet  
517:         
518:       .PARAMETER StandbyTimeoutDC  
519:            Amount of time before a computer is put on standby while on battery power  
520:         
521:       .PARAMETER HibernateTimeoutAC  
522:            Amount of time before a computer is put in hibernation while plugged into AC outlet  
523:         
524:       .PARAMETER HibernateTimeoutDC  
525:            Amount of time before a computer is put in hibernation while on battery power  
526:         
527:       .EXAMPLE  
528:            PS C:\> Set-PowerSchemeSettings -MonitorTimeoutAC $value1 -MonitorTimeoutDC $value2  
529:         
530:       .NOTES  
531:            Additional information about the function.  
532:  #>  
533:         
534:       [CmdletBinding()]  
535:       param  
536:       (  
537:            [string]  
538:            $MonitorTimeoutAC,  
539:            [string]  
540:            $MonitorTimeoutDC,  
541:            [string]  
542:            $DiskTimeoutAC,  
543:            [string]  
544:            $DiskTimeoutDC,  
545:            [string]  
546:            $StandbyTimeoutAC,  
547:            [string]  
548:            $StandbyTimeoutDC,  
549:            [string]  
550:            $HibernateTimeoutAC,  
551:            [string]  
552:            $HibernateTimeoutDC  
553:       )  
554:         
555:       $Scheme = Get-PowerScheme  
556:       If (($MonitorTimeoutAC -ne $null) -and ($MonitorTimeoutAC -ne "")) {  
557:            Write-Host "Setting monitor timeout on AC to"$MonitorTimeoutAC" minutes....." -NoNewline  
558:            $Switches = "/change" + [char]32 + "monitor-timeout-ac" + [char]32 + $MonitorTimeoutAC  
559:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\7516b95f-f776-4464-8c53-06167f40cc99\3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e"  
560:            $TestValue = $MonitorTimeoutAC  
561:            $PowerIndex = "ACSettingIndex"  
562:       }  
563:       If (($MonitorTimeoutDC -ne $null) -and ($MonitorTimeoutDC -ne "")) {  
564:            Write-Host "Setting monitor timeout on DC to"$MonitorTimeoutDC" minutes....." -NoNewline  
565:            $Switches = "/change" + [char]32 + "monitor-timeout-dc" + [char]32 + $MonitorTimeoutDC  
566:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\7516b95f-f776-4464-8c53-06167f40cc99\3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e"  
567:            $TestValue = $MonitorTimeoutDC  
568:            $PowerIndex = "DCSettingIndex"  
569:       }  
570:       If (($DiskTimeoutAC -ne $null) -and ($DiskTimeoutAC -ne "")) {  
571:            Write-Host "Setting disk timeout on AC to"$DiskTimeoutAC" minutes....." -NoNewline  
572:            $Switches = "/change" + [char]32 + "disk-timeout-ac" + [char]32 + $DiskTimeoutAC  
573:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\0012ee47-9041-4b5d-9b77-535fba8b1442\6738e2c4-e8a5-4a42-b16a-e040e769756e"  
574:            $TestValue = $DiskTimeoutAC  
575:            $PowerIndex = "ACSettingIndex"  
576:       }  
577:       If (($DiskTimeoutDC -ne $null) -and ($DiskTimeoutDC -ne "")) {  
578:            Write-Host "Setting disk timeout on DC to"$DiskTimeoutDC" minutes....." -NoNewline  
579:            $Switches = "/change" + [char]32 + "disk-timeout-dc" + [char]32 + $DiskTimeoutDC  
580:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\0012ee47-9041-4b5d-9b77-535fba8b1442\6738e2c4-e8a5-4a42-b16a-e040e769756e"  
581:            $TestValue = $DiskTimeoutDC  
582:            $PowerIndex = "DCSettingIndex"  
583:       }  
584:       If (($StandbyTimeoutAC -ne $null) -and ($StandbyTimeoutAC -ne "")) {  
585:            Write-Host "Setting standby timeout on AC to"$StandbyTimeoutAC" minutes....." -NoNewline  
586:            $Switches = "/change" + [char]32 + "standby-timeout-ac" + [char]32 + $StandbyTimeoutAC  
587:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\238c9fa8-0aad-41ed-83f4-97be242c8f20\29f6c1db-86da-48c5-9fdb-f2b67b1f44da"  
588:            $TestValue = $StandbyTimeoutAC  
589:            $PowerIndex = "ACSettingIndex"  
590:       }  
591:       If (($StandbyTimeoutDC -ne $null) -and ($StandbyTimeoutDC -ne "")) {  
592:            Write-Host "Setting standby timeout on DC to"$StandbyTimeoutDC" minutes....." -NoNewline  
593:            $Switches = "/change" + [char]32 + "standby-timeout-dc" + [char]32 + $StandbyTimeoutDC  
594:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\238c9fa8-0aad-41ed-83f4-97be242c8f20\29f6c1db-86da-48c5-9fdb-f2b67b1f44da"  
595:            $TestValue = $StandbyTimeoutDC  
596:            $PowerIndex = "DCSettingIndex"  
597:       }  
598:       If (($HibernateTimeoutAC -ne $null) -and ($HibernateTimeoutAC -ne "")) {  
599:            Write-Host "Setting hibernate timeout on AC to"$HibernateTimeoutAC" minutes....." -NoNewline  
600:            $Switches = "/change" + [char]32 + "hibernate-timeout-ac" + [char]32 + $HibernateTimeoutAC  
601:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\238c9fa8-0aad-41ed-83f4-97be242c8f20\9d7815a6-7ee4-497e-8888-515a05f02364"  
602:            [int]$TestValue = $HibernateTimeoutAC  
603:            $PowerIndex = "ACSettingIndex"  
604:       }  
605:       If (($HibernateTimeoutDC -ne $null) -and ($HibernateTimeoutDC -ne "")) {  
606:            Write-Host "Setting hibernate timeout on DC to"$HibernateTimeoutDC" minutes....." -NoNewline  
607:            $Switches = "/change" + [char]32 + "hibernate-timeout-dc" + [char]32 + $HibernateTimeoutDC  
608:            $TestKey = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + $Scheme.GUID + "\238c9fa8-0aad-41ed-83f4-97be242c8f20\9d7815a6-7ee4-497e-8888-515a05f02364"  
609:            $TestValue = $HibernateTimeoutDC  
610:            $PowerIndex = "DCSettingIndex"  
611:       }  
612:       $ErrCode = (Start-Process -FilePath "powercfg.exe" -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode  
613:       $RegValue = (((Get-ItemProperty $TestKey).$PowerIndex) /60)  
614:       #Round down to the nearest tenth due to hibernate values being 1 decimal off  
615:       $RegValue = $RegValue - ($RegValue % 10)  
616:       If (($RegValue -eq $TestValue) -and ($ErrCode -eq 0)) {  
617:            Write-Host "Success" -ForegroundColor Yellow  
618:            $Errors = $false  
619:       } else {  
620:            Write-Host "Failed" -ForegroundColor Red  
621:            $Errors = $true  
622:       }  
623:       Return $Errors  
624:  }  
625:    
626:    
627:  cls  
628:  #Set Errors variable to false to begin the script with no errors  
629:  $Errors = $false  
630:  #Set the title of the PowerShell console  
631:  Set-ConsoleTitle -Title $ConsoleTitle  
632:    
633:  <#Hardcoded Power Scheme Settings  
634:  $Errors = Set-PowerSchemeSettings -MonitorTimeoutAC 120  
635:  $Errors = Set-PowerSchemeSettings -MonitorTimeoutDC 120  
636:  $Errors = Set-PowerSchemeSettings -DiskTimeOutAC 120  
637:  $Errors = Set-PowerSchemeSettings -DiskTimeOutDC 120  
638:  $Errors = Set-PowerSchemeSettings -StandbyTimeoutAC 120  
639:  $Errors = Set-PowerSchemeSettings -StandbyTimeoutDC 120  
640:  $Errors = Set-PowerSchemeSettings -HibernateTimeoutAC 60  
641:  $Errors = Set-PowerSchemeSettings -HibernateTimeoutDC 60  
642:  #>  
643:    
644:  #Generate a report if -Report is specified  
645:  If ($Report.IsPresent) {  
646:       Publish-Report  
647:  }  
648:  #Set the Power Scheme to Balanced  
649:  If ($Balanced.IsPresent) {  
650:       $Errors = Set-PowerScheme -PowerScheme 'Balanced'  
651:  }  
652:  #Set the Power Scheme to Power Saver  
653:  If ($PowerSaver.IsPresent) {  
654:       $Errors = Set-PowerScheme -PowerScheme 'Power Saver'  
655:  }  
656:  #Set the Power Scheme to High Performance  
657:  If ($HighPerformance.IsPresent) {  
658:       $Errors = Set-PowerScheme -PowerScheme 'High Performance'  
659:  }  
660:  #Set the Power Scheme to Custom  
661:  If (($Custom -ne $null) -and ($Custom -ne "")) {  
662:       $Errors = Set-PowerScheme -PowerScheme $Custom  
663:  }  
664:  #Import a power scheme  
665:  If (($ImportPowerSchemeFile -ne $null) -and ($ImportPowerSchemeFile -ne "")) {  
666:       If ($SetImportedPowerSchemeDefault.IsPresent) {  
667:            $Errors = Import-PowerScheme -File $ImportPowerSchemeFile -PowerSchemeName $PowerSchemeName -SetActive  
668:       } else {  
669:            $Errors = Import-PowerScheme -File $ImportPowerSchemeFile -PowerSchemeName $PowerSchemeName  
670:       }  
671:  }  
672:  #Set individual power scheme setting from command line  
673:  If (($SetPowerSchemeSetting -ne $null) -and ($SetPowerSchemeSetting -ne "")) {  
674:       switch ($SetPowerSchemeSetting) {  
675:            "MonitorTimeoutAC" { $Errors = Set-PowerSchemeSettings -MonitorTimeoutAC $SetPowerSchemeSettingValue }  
676:            "MonitorTimeoutDC" { $Errors = Set-PowerSchemeSettings -MonitorTimeoutDC $SetPowerSchemeSettingValue }  
677:            "DiskTimeOutAC" { $Errors = Set-PowerSchemeSettings -DiskTimeOutAC $SetPowerSchemeSettingValue }  
678:            "DiskTimeOutDC" { $Errors = Set-PowerSchemeSettings -DiskTimeOutDC $SetPowerSchemeSettingValue }  
679:            "StandbyTimeoutAC" { $Errors = Set-PowerSchemeSettings -StandbyTimeoutAC $SetPowerSchemeSettingValue }  
680:            "StandbyTimeoutDC" { $Errors = Set-PowerSchemeSettings -StandbyTimeoutDC $SetPowerSchemeSettingValue }  
681:            "HibernateTimeoutAC" { $Errors = Set-PowerSchemeSettings -HibernateTimeoutAC $SetPowerSchemeSettingValue }  
682:            "HibernateTimeoutDC" { $Errors = Set-PowerSchemeSettings -HibernateTimeoutDC $SetPowerSchemeSettingValue }  
683:       }  
684:  }  
685:  #Exit with an error code 5 if errors were encountered during any of the power settings  
686:  If ($Errors -eq $true) {  
687:       Exit 5  
688:  }  
689:    

23 August 2016

PowerShell: Import Active Directory Extension Attributes

Recently, my firm has started using the AD extension attributes for custom data. I wrote a script a while back that read the data from a .CSV and imported it into a specific ExtensionAttribute. This came up again and it was brought to my attention that we may start using more of the fields. In order to automate this so that I don't have to modify the script each time, I decided to write one that gets all of its information from the .CSV file. With the help of Sapien's PowerShell Studio, this script was easy to write and easy to document.

This script will import a populated .CSV file that resides in the same directory as the script. The first line in the .CSV file contains the headers. The script reads that line to know what fields to populate in AD. You enter the exact extensionAttribute name in the header field and all data beneath it will be populated in that field for each user. Here is a screenshot of a sample .CSV file.


The first line is what tells it which extension attribute to populate the data to. It can have as many attribute lines as you need. I also added the features to remove data from an extension attribute. If a cell is left blank or CLEAR is input into the cell, the extension attribute will be cleared. If you do not want anything done to an attribute, input NO CLEAR into the cell and the script will not do anything to the attribute.

Logging has also been added to the script. If you want a log file written, then populate the -LogFile parameter. If this is left unpopulated, the script will not create a log file.

Another feature it -ProcessDelay. I added this as a precautionary feature. This puts a specified XX seconds delay between each record. This allows you to slowly implement the changes in AD without it all happening at once. Say you accidentally made an error in the .CSV file and discover it midway through. At least not all records got changed at that time. If you leave that field blank, the script will not pause between each record.

The final feature I added was for the script to go back and verify the change was actually made.

I do highly recommend that you test this script out on a test account first before using it against all of your user accounts. It works in my environment, but it is no guarantee that it will work in yours.

Here is a screenshot of the script in action.



You can download the script from here.




1:  <#  
2:       .SYNOPSIS  
3:            A brief description of the ImportADExtensions.ps1 file.  
4:         
5:       .DESCRIPTION  
6:            This script will import data from a CSV file to be written to the desired extension attributes in active directory  
7:         
8:       .PARAMETER DataFile  
9:            Name of the csv file that contains the data to import into active directory  
10:         
11:       .PARAMETER LogFile  
12:            Name of the log file to write the status of each change to  
13:         
14:       .PARAMETER ProcessDelay  
15:            This will pause the script for XX number of seconds before processing the next AD user. This is intended as a safety measure in the event that wrong data is being written to each AD profile. This allows for not all profile to be affected at once.  
16:         
17:       .EXAMPLE  
18:            Run with no logging and no delays between entry changes  
19:                 powershell.exe -executionpolicy bypass -file ImportADExtensions.ps1 -DataFile Data.csv  
20:    
21:            Run with logging and no delays between entry changes  
22:                 powershell.exe -executionpolicy bypass -file ImportADExtensions.ps1 -DataFile Data.csv -LogFile ADExtensions.log  
23:    
24:            Run with logging and 10 second delay between entry changes  
25:                 powershell.exe -executionpolicy bypass -file ImportADExtensions.ps1 -DataFile Data.csv -LogFile ADExtensions.log -ProcessDelay 10  
26:    
27:            You can also pre-populate the parameters within the Param fields inside the script.  
28:    
29:       .NOTES  
30:            ===========================================================================  
31:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.126  
32:            Created on:       7/28/2016 8:55 AM  
33:            Created by:       Mick Pletcher  
34:            Organization:  
35:            Filename:         ImportADExtensions.ps1  
36:            ===========================================================================  
37:  #>  
38:  [CmdletBinding()]  
39:  param  
40:  (  
41:       [ValidateNotNullOrEmpty()][string]  
42:       $DataFile,  
43:       [string]  
44:       $LogFile,  
45:       [int]  
46:       $ProcessDelay  
47:  )  
48:    
49:  function Get-RelativePath {  
50:  <#  
51:       .SYNOPSIS  
52:            Get the relative path  
53:         
54:       .DESCRIPTION  
55:            Returns the location of the currently running PowerShell script  
56:         
57:       .NOTES  
58:            Additional information about the function.  
59:  #>  
60:         
61:       [CmdletBinding()][OutputType([string])]  
62:       param ()  
63:         
64:       $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
65:       Return $Path  
66:  }  
67:    
68:  function Import-DataFile {  
69:  <#  
70:       .SYNOPSIS  
71:            Import data file  
72:         
73:       .DESCRIPTION  
74:            Import the data from a csv file  
75:         
76:       .EXAMPLE  
77:            PS C:\> Import-DataFile  
78:         
79:       .NOTES  
80:            Additional information about the function.  
81:  #>  
82:         
83:       [CmdletBinding()][OutputType([object])]  
84:       param ()  
85:         
86:       #Get the path this script is being executed from  
87:       $RelativePath = Get-RelativePath  
88:       #Associate the relative path with the data file to be imported  
89:       $File = $RelativePath + $DataFile  
90:       #Read the data file to a variable  
91:       $FileData = Get-Content -Path $File -Force  
92:       #Get the attribute fields  
93:       $Fields = ($FileData[0]).Split(",")  
94:       $ImportedRecords = @()  
95:       foreach ($Record in $FileData) {  
96:            If ($Record -notlike "*extensionattribute*") {  
97:                 $SplitRecord = $Record.Split(",")  
98:                 $objRecord = New-Object System.Management.Automation.PSObject  
99:                 for ($i = 0; $i -lt $Fields.Length; $i++) {  
100:                      $objRecord | Add-Member -type NoteProperty -Name $Fields[$i] -Value $SplitRecord[$i]  
101:                 }  
102:                 $ImportedRecords += $objRecord  
103:            }  
104:       }  
105:       Return $ImportedRecords  
106:  }  
107:    
108:  function New-Logfile {  
109:  <#  
110:       .SYNOPSIS  
111:            Create a new log file  
112:         
113:       .DESCRIPTION  
114:            This will create a new log file. If an old one exists, it will delete it.  
115:         
116:       .EXAMPLE  
117:                      PS C:\> New-Logfile  
118:         
119:       .NOTES  
120:            Additional information about the function.  
121:  #>  
122:         
123:       [CmdletBinding()]  
124:       param ()  
125:         
126:       $RelativePath = Get-RelativePath  
127:       $Logs = $RelativePath + $LogFile  
128:       If ((Test-Path $Logs) -eq $true) {  
129:            $Output = "Deleting old log file....."  
130:            Remove-Item -Path $Logs -Force | Out-Null  
131:            If ((Test-Path $Logs) -eq $false) {  
132:                 $Output += "Success" + "`n"  
133:            } else {  
134:                 $Output += "Failed" + "`n"  
135:            }  
136:       }  
137:       If (($LogFile -ne "") -and ($LogFile -ne $null)) {  
138:            $Output += "Creating new log file....."  
139:            New-Item -Path $Logs -ItemType File -Force | Out-Null  
140:            If ((Test-Path $Logs) -eq $true) {  
141:                 $Output += "Success"  
142:            } else {  
143:                 $Output += "Failed"  
144:            }  
145:            Write-Output $Output  
146:       }  
147:  }  
148:    
149:  function Write-ExtensionAttributes {  
150:  <#  
151:       .SYNOPSIS  
152:            Write Extension Attributes to Active Directory  
153:         
154:       .DESCRIPTION  
155:            This script will write the extension attributes to active directory. It reads the name of the object field to associate with the correct extension attribute in AD.  
156:         
157:       .PARAMETER Records  
158:            List of imported objects  
159:         
160:       .EXAMPLE  
161:            PS C:\> Write-ExtensionAttributes -Records $value1  
162:         
163:       .NOTES  
164:            Additional information about the function.  
165:  #>  
166:         
167:       [CmdletBinding()]  
168:       param  
169:       (  
170:            [ValidateNotNullOrEmpty()][object]  
171:            $Records  
172:       )  
173:         
174:       #Get all member of $Records  
175:       $Fields = $Records | Get-Member  
176:       #Filter for just the extension attribute properties  
177:       $Fields = ($Fields | Where-Object { (($_.MemberType -eq "NoteProperty") -and ($_.Name -like "*extensionattribute*")) }).name  
178:       for ($i = 0; $i -lt @($Records).Count; $i++) {  
179:            #Get all active directory properties for specified user  
180:            $User = Get-ADUser $Records[$i].Username -Properties *  
181:            $Output += "User " + ($i+1) + " of " + @($Records).Count + "`n"  
182:            $Output += "Username: " + $Records[$i].Username + "`n"  
183:            foreach ($Field in $Fields) {  
184:                 $Output += $Field + ": " + $Records[$i].$Field + "`n"  
185:                 If ((($Records[$i].$Field -eq "Clear") -or ($Records[$i].$Field -eq "") -or ($Records[$i].$Field -eq $null)) -and ($Records[$i].$Field -ne "NO CLEAR")) {  
186:                      $Output += "Clearing " + $Field + "....."  
187:                      Set-ADUser -Identity $Records[$i].Username -Clear $Field  
188:                      #Get the field that was change from active directory  
189:                      $Test = Get-ADUser $Records[$i].Username -Properties * | select $Field  
190:                      #Test if the data in the AD field matches the data from the imported file  
191:                      if ($Test.$Field -eq $null) {  
192:                           $Output += "Success" + "`n"  
193:                      } else {  
194:                           $Output += "Failed" + "`n"  
195:                      }  
196:                 } elseif ($Records[$i].$Field -ne "NO CLEAR") {  
197:                      $User.$Field = $Records[$i].$Field  
198:                      $Output += "Setting " + $Field + "....."  
199:                      #Write change to active directory  
200:                      Set-ADUser -Instance $User  
201:                      #Get the field that was change from active directory  
202:                      $Test = Get-ADUser $Records[$i].Username -Properties * | select $Field  
203:                      #Test if the data in the AD field matches the data from the imported file  
204:                      if ($Test.$Field -eq $Records[$i].$Field) {  
205:                           $Output += "Success" + "`n"  
206:                      } else {  
207:                           $Output += "Failed" + "`n"  
208:                      }  
209:                 }  
210:            }  
211:            Write-Output $Output  
212:            #If the Logfile parameter is populated, then write the output to a logfile  
213:            If (($LogFile -ne "") -and ($LogFile -ne $null)) {  
214:                 #Get the path where this script is being executed from  
215:                 $RelativePath = Get-RelativePath  
216:                 #Define the log file path  
217:                 $Logs = $RelativePath + $LogFile  
218:                 #Write the output to the log file  
219:                 Add-Content -Value $Output -Path $Logs -Encoding UTF8 -Force  
220:            }  
221:            $Output = $null  
222:            If (($ProcessDelay -ne $null) -and ($ProcessDelay -ne "")) {  
223:                 Start-Sleep -Seconds $ProcessDelay  
224:            }  
225:            cls  
226:       }  
227:  }  
228:    
229:  Import-Module -Name ActiveDirectory  
230:  #Delete old log file and create a new one  
231:  New-Logfile  
232:  #Import all records from the csv file  
233:  $Records = Import-DataFile  
234:  #Apply changes to active directory  
235:  Write-ExtensionAttributes -Records $Records  
236:    

05 August 2016

SCCM Automated Endpoint Full System Scan upon Infection with Email Notification

While helping to manage Microsoft Endpoint, a former colleague suggested that I setup Endpoint to automatically run a full system scan each time an infection is detected. I Googled the blog posting on it and although it is a great post, I figured it could streamlined even more by just using SCCM alone to achieve the same outcome. It is nice when you are out of the office and your backup might not have the time to keep an eye on the antivirus infections. It is also beneficial if you are in an environment where the office is closed overnight and on weekends. If an infection takes place, this can help remediate it without your presence.

This is the third edition. The first edition just initiated a full system scan upon infection. The second edition initiated a full system scan plus would send an email to the designated email address. The third edition now combines the first two and allows for them to be designated using parameters. The code has also been optimized.

I decided to use the SCCM custom application detection to query a system and see if a full system scan has been performed in the event of an infection logged in the event viewer. I first started out by writing a PowerShell script that would perform a WMI query on the SCCM server for the status of the system the application detection was being run on. The problem I ran across was that the application is being run under system credentials, which would require me to pass network credentials within the script. Instead of having to do this, I decided to query the event viewer logs on the local machine to look for the last infection date/time, which is event 1116. I also found that event 1001 and provider name Microsoft Antimalware designate are used when a system scan has been performed. Here are the steps SCCM and PowerShell go through:

  1. SCCM deploys the package to the system.
  2. The application detection queries the event viewer logs for the last 1116 ID (infection).
  3. The application detection queries the event viewer logs for the last 1001 ID and "Microsoft Antimalware" provider name.
  4. If a system 1001 ID does not exist since the last infection, the custom detection method will exit out as a failure.
  5. If the custom detection failed, the AntiVirusScanEmail.ps1 file will be executed on the machine.
  6. An email is sent that tells a scan was performed on %COMPUTERNAME% with the virus details in the body if -Email switch was specified
  7. Once the scan is complete, an application deployment evaluation cycle is initiated to update the SCCM server with the status of the system.
  8. The application detection is initiated again to confirm the scan occurred.
If you desire emails be sent alerting you of an infection and system scan, then you will need to download and place PsExec.exe in the same directory as this script. The next thing will be to define the Installation program in SCCM using psexec.exe. This allows the PowerShell script to be executed under a domain account, thereby giving it the ability to use the send-mailmessage commandlet.  Here is how to do this:

psexec.exe \\%computername% -u <domain>\<username> -p <password> -h cmd.exe /c "echo . | powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1"

Do not change %computername%. The only parts of the psexec.exe command line that need to be changed are <domain>, <username>, and <password>. 

If you do not want emails sent, then you can use the following command line parameter:

powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1 -FullScan
or
powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1 -QuickScan

This is setup in SCCM as a normal application deployment. The only thing that differs from a standard deployment is the application detection method. The ApplicationVirusDetectionMethodEmail.ps1 script is imported in for the detection method. The AntiVirusScanEmail.ps1 file is setup as the installation program. I have mine entered like this:

powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1 -FullScan

You can see the settings in this video:




If you also want it to email you, then refer to the section above on using psexec.exe and the example to enter in as the installation program. 

One more thing is that I have the application hidden from the software center. There really isn't a need for it to be seen by the end-users. 

In order for this to work in a timely manor, you will need to change the software deployment frequency under the client settings. I have mine set at every 8 hours, or three times a day.



You can download the application and application detection files from the following links:



ApplicationVirusDetectionMethodEmail.ps1

1:  <#       
2:       .NOTES  
3:       ===========================================================================  
4:        Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127  
5:        Created on:       8/5/2016 11:11 AM  
6:        Created by:       Mick Pletcher  
7:        Organization:         
8:        Filename:         ApplicationVirusDetectionMethod.ps1  
9:       ===========================================================================  
10:       
11:  #>  
12:    
13:    
14:  $LastInfection = get-winevent -filterhashtable @{ logname = 'system'; ID = 1116 } -maxevents 1 -ErrorAction SilentlyContinue  
15:  $LastScan = Get-WinEvent -FilterHashtable @{ logname = 'system'; ProviderName = 'Microsoft Antimalware'; ID = 1001 } -MaxEvents 1  
16:  If ($LastScan.TimeCreated -lt $LastInfection.TimeCreated) {  
17:       #No scan since last infection  
18:       Start-Sleep -Seconds 5  
19:       exit 0  
20:  } else {  
21:       #No infection since last scan  
22:       Write-Host "No Infection"  
23:       Start-Sleep -Seconds 5  
24:       exit 0  
25:  }  
26:    


AntiVirusScanEmail.ps1

1:  <#  
2:       .SYNOPSIS  
3:            EndPoint Virus Scan  
4:         
5:       .DESCRIPTION  
6:            This script will initiate a full or quick scan, whichever switch is selected at the command line. Once the scan is completed, it will check the event viewer logs for a scan completed entry to verify the scan successfully completed. If the Email switch is designated at the command line, then an email is sent to the specified recipient. It is suggested the $EmailRecipient, $EmailSender, and $SMTPServer be predefined in the parameter field. I have also included a trigger of the application deployment evaluation cycle to expedite the process.  
7:         
8:       .PARAMETER FullScan  
9:            Initiate a full system scan  
10:         
11:       .PARAMETER QuickScan  
12:            Initiate a quick scan  
13:         
14:       .PARAMETER Email  
15:            Select if you want an email report sent to the specified email address  
16:         
17:       .PARAMETER EmailRecipient  
18:            Receipient's email address  
19:         
20:       .PARAMETER EmailSender  
21:            Sender's email address  
22:         
23:       .PARAMETER SMTPServer  
24:            SMTP server address  
25:         
26:       .EXAMPLE  
27:            Initiate a Quickscan  
28:            powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1 -QuickScan  
29:              
30:            Initiate a Fullscan  
31:            powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1 -FullScan  
32:              
33:            Initiate a Fullscan and send email report. To, From, and SMTP parameters are pre-defined  
34:            powershell.exe -executionpolicy bypass -file AntiVirusScanEmail.ps1 -FullScan -Email  
35:         
36:       .NOTES  
37:            ===========================================================================  
38:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127  
39:            Created on:       8/5/2016 11:11 AM  
40:            Created by:       Mick Pletcher  
41:            Organization:  
42:            Filename:         AntiVirusScanEmail.ps1  
43:            ===========================================================================  
44:  #>  
45:  param  
46:  (  
47:       [switch]  
48:       $FullScan,  
49:       [switch]  
50:       $QuickScan,  
51:       [switch]  
52:       $Email,  
53:       [string]  
54:       $EmailRecipient = '',  
55:       [string]  
56:       $EmailSender = '',  
57:       [string]  
58:       $SMTPServer = ''  
59:  )  
60:    
61:  #Import the Endpoint Provider module  
62:  Import-Module $env:ProgramFiles"\Microsoft Security Client\MpProvider\MpProvider.psd1"  
63:  #Get the relative execution path of this script  
64:  $RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
65:  #Find the last infection entry in the event viewer logs  
66:  $LastInfection = get-winevent -filterhashtable @{ logname = 'system'; ID = 1116 } -maxevents 1 -ErrorAction SilentlyContinue  
67:  #Full Scan  
68:  If ($FullScan.IsPresent) {  
69:       #Initiate a full system scan  
70:       Start-MProtScan -ScanType "FullScan"  
71:       #Commented area only there if you want to manually execute this script to watch it execute  
72:  <#     cls  
73:       Write-Warning "Error: $_"  
74:       Write-Host $_.Exception.ErrorCode  
75:  #>       
76:       #Get the last event viewer log written by Endpoint to check if the full system scan has finished  
77:       $LastScan = Get-WinEvent -FilterHashtable @{ logname = 'system'; ProviderName = 'Microsoft Antimalware'; ID = 1001 } -MaxEvents 1  
78:       #  
79:       If ($LastScan.Message -like '*Microsoft Antimalware scan has finished*') {  
80:            $EmailBody = "An Endpoint antimalware full system scan has been performed on" + [char]32 + $env:COMPUTERNAME + [char]32 + "due to the virus detection listed below." + [char]13 + [char]13 + $LastInfection.Message  
81:       } else {  
82:            $EmailBody = "An Endpoint antimalware full system scan did not complete on" + [char]32 + $env:COMPUTERNAME + [char]32 + "due to the virus detection listed below." + [char]13 + [char]13 + $LastInfection.Message  
83:       }  
84:  }  
85:  #Quick Scan  
86:  If ($QuickScan.IsPresent) {  
87:       #Initiate a quick system scan  
88:       Start-MProtScan -ScanType "QuickScan"  
89:       #Commented area only there if you want to manually execute this script to watch it execute  
90:  <#     cls  
91:       Write-Warning "Error: $_"  
92:       Write-Host $_.Exception.ErrorCode  
93:  #>       
94:       #Get the last event viewer log written by Endpoint to check if the quick system scan has finished  
95:       $LastScan = Get-WinEvent -FilterHashtable @{ logname = 'system'; ProviderName = 'Microsoft Antimalware'; ID = 1001 } -MaxEvents 1  
96:       #  
97:       If ($LastScan.Message -like '*Microsoft Antimalware scan has finished*') {  
98:            $EmailBody = "An Endpoint antimalware quick system scan has been performed on" + [char]32 + $env:COMPUTERNAME + [char]32 + "due to the virus detection listed below." + [char]13 + [char]13 + $LastInfection.Message  
99:       } else {  
100:            $EmailBody = "An Endpoint antimalware quick system scan did not complete on" + [char]32 + $env:COMPUTERNAME + [char]32 + "due to the virus detection listed below." + [char]13 + [char]13 + $LastInfection.Message  
101:       }  
102:  }  
103:  #Email Infection Report  
104:  If ($Email.IsPresent) {  
105:       $Subject = "Microsoft Endpoint Infection Report"  
106:       $EmailSubject = "Virus Detection Report for" + [char]32 + $env:COMPUTERNAME  
107:       Send-MailMessage -To $EmailRecipient -From $EmailSender -Subject $Subject -Body $EmailBody -SmtpServer $SMTPServer  
108:  }  
109:  #Initiate Application Deployment Evaluation Cycle  
110:  $WMIPath = "\\" + $env:COMPUTERNAME + "\root\ccm:SMS_Client"  
111:  $SMSwmi = [wmiclass]$WMIPath  
112:  $strAction = "{00000000-0000-0000-0000-000000000121}"  
113:  [Void]$SMSwmi.TriggerSchedule($strAction)  
114:    

02 August 2016

PowerShell: SCCM Client Installer

It was time to upgrade the old SCCM Client Installer. I wanted to add new features and also make the script be able to execute by using parameters. With the help of Sapien's PowerShell Studio, this was very easy to do! I did not include all optional fields, but for my own company, this works great and you can easily modify it if there are additional parameters of the client installer you need in your environment.

The first thing I did was to make the parameters available for the client installer directory, client installer filename, management point, SMS site code, and two switches defining whether to install or uninstall. I centered this script around using the ccmsetup.exe file to both install and uninstall. Another feature I added is the Build parameter. This parameter can be added with the install parameter, or by itself. This configures the SCCM client in preparation for a sysprep of the computer. This is primarily done before sysprepping a machine using SCCM or MDT that captures a reference image. This allows for the client to be already present when the reference image is laid down on a newly imaged computer. It then communicates back to the SCCM server to configure itself as a new client when that reference image is laid down on new machines. I have included command line examples within the script's documentation.

This is a video of the script executing to uninstall the SCCM client on a machine.





This video shows the script installing the SCCM client. You can see that I include the uninstallation of the application first so that if this is being run to fix a machine, it will first uninstall and then install the client. Another thing you will see is that it initializes the installation and then goes on to wait for the installation to complete. I put the waiting in there because once the client begins its install, it closes out the first instance of the ccmsetup.exe and opens up a new one, thereby making the script think the installation finished, when in essence, it did not. I make the script wait because I want to make sure that when the install takes place, it is completely finished when the script closes. When the script executes during a build, especially during a reference image build, it needs to time complete the setup before the sysprep. That is the purpose of the wait. If you do not want it to wait, you can disable lines 264 and 280.


Finally, below is a pic of the script being executed to prepare the SCCM client for a sysprep. I have also included a pop-up window in the event the SCCM client is not properly prepared for the sysprep. The window will stay there until OK is clicked. This gives the admin the capability of either manually deleting the file and registry keys and stopping the ccmexec service, or restarting the build. 



You can download the script from here.



1:  <#  
2:       .SYNOPSIS  
3:            Install SCCM Client  
4:         
5:       .DESCRIPTION  
6:            Uninstall any old client and install the new SCCM client  
7:         
8:       .PARAMETER Build  
9:            Select if this is being executed while building a reference image  
10:         
11:       .PARAMETER ClientInstallationDirectory  
12:            Directory where the client ClientInstallationFile is located  
13:         
14:       .PARAMETER ClientInstallationFile  
15:            SCCM ClientInstallationFile  
16:         
17:       .PARAMETER Install  
18:            A description of the Install parameter.  
19:         
20:       .PARAMETER ManagementPoint  
21:            SCCM Management Point  
22:         
23:       .PARAMETER SMSSiteCode  
24:            SMS Site Code  
25:         
26:       .PARAMETER Uninstall  
27:            A description of the Uninstall parameter.  
28:         
29:       .PARAMETER UsePKICert  
30:            Specifies whether clients use PKI certificate when available  
31:         
32:       .PARAMETER NOCRLCheck  
33:            Specifies that clients do not check the certificate revocation list (CRL) for site systems  
34:         
35:       .PARAMETER Source  
36:            Specifies the source location from which to download installation files. This can be a local or UNC path.  
37:         
38:       .EXAMPLE  
39:            New installation  
40:            powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Install  
41:              
42:            Uninstall  
43:            powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Uninstall  
44:              
45:            SCCM/MDT/Sysprep  
46:            powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Build  
47:            powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Install -Build  
48:         
49:       .NOTES  
50:            The above examples do not include the $ClientInstallationDirectory and  
51:            the $ClientInsallationFile. I prepopulated the data within the parameter  
52:            definitions below. I also define the $ManagementPoint and $SMSSiteCode. I  
53:            have not tested the $UsePKICert, $NOCRLCheck, or $Source fields as we do  
54:            not use those where I work, therefore I cannot verify if they are valid.  
55:    
56:            ===========================================================================  
57:             Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127  
58:             Created on:        8/2/2016 2:50 PM  
59:             Created by:        Mick Pletcher  
60:             Organization:         
61:             Filename:          SCCMClientInstaller.ps1  
62:            ===========================================================================  
63:    
64:  #>  
65:  [CmdletBinding()]  
66:  param  
67:  (  
68:       [switch]  
69:       $Build,  
70:       [ValidateNotNullOrEmpty()][string]  
71:       $ClientInstallationDirectory = '',  
72:       [ValidateNotNullOrEmpty()][string]  
73:       $ClientInstallationFile = 'ccmsetup.exe',  
74:       [switch]  
75:       $Install,  
76:       [string]  
77:       $ManagementPoint = '',  
78:       [string]  
79:       $SMSSiteCode = '',  
80:       [switch]  
81:       $Uninstall,  
82:       [switch]  
83:       $UsePKICert,  
84:       [switch]  
85:       $NOCRLCheck,  
86:       [string]  
87:       $Source  
88:  )  
89:    
90:    
91:  function Get-MetaData {  
92:  <#  
93:       .SYNOPSIS  
94:            Get File MetaData  
95:         
96:       .DESCRIPTION  
97:            A detailed description of the Get-MetaData function.  
98:         
99:       .PARAMETER FileName  
100:            Name of File  
101:         
102:       .EXAMPLE  
103:            PS C:\> Get-MetaData -FileName 'Value1'  
104:         
105:       .NOTES  
106:            Additional information about the function.  
107:  #>  
108:         
109:       [CmdletBinding()][OutputType([object])]  
110:       param  
111:       (  
112:            [ValidateNotNullOrEmpty()][string]  
113:            $FileName  
114:       )  
115:         
116:       Write-Host "Retrieving File Description Data....." -NoNewline  
117:       $MetaDataObject = New-Object System.Object  
118:       $shell = New-Object -COMObject Shell.Application  
119:       $folder = Split-Path $FileName  
120:       $file = Split-Path $FileName -Leaf  
121:       $shellfolder = $shell.Namespace($folder)  
122:       $shellfile = $shellfolder.ParseName($file)  
123:       $MetaDataProperties = 0..287 | Foreach-Object { '{0} = {1}' -f $_, $shellfolder.GetDetailsOf($null, $_) }  
124:       For ($i = 0; $i -le 287; $i++) {  
125:            $Property = ($MetaDataProperties[$i].split("="))[1].Trim()  
126:            $Property = (Get-Culture).TextInfo.ToTitleCase($Property).Replace(' ', '')  
127:            $Value = $shellfolder.GetDetailsOf($shellfile, $i)  
128:            If ($Property -eq 'Attributes') {  
129:                 switch ($Value) {  
130:                      'A' {  
131:                           $Value = 'Archive (A)'  
132:                      }  
133:                      'D' {  
134:                           $Value = 'Directory (D)'  
135:                      }  
136:                      'H' {  
137:                           $Value = 'Hidden (H)'  
138:                      }  
139:                      'L' {  
140:                           $Value = 'Symlink (L)'  
141:                      }  
142:                      'R' {  
143:                           $Value = 'Read-Only (R)'  
144:                      }  
145:                      'S' {  
146:                           $Value = 'System (S)'  
147:                      }  
148:                 }  
149:            }  
150:            #Do not add metadata fields which have no information  
151:            If (($Value -ne $null) -and ($Value -ne '')) {  
152:                 $MetaDataObject | Add-Member -MemberType NoteProperty -Name $Property -Value $Value  
153:            }  
154:       }  
155:       [string]$FileVersionInfo = (Get-ItemProperty $FileName).VersionInfo  
156:       $SplitInfo = $FileVersionInfo.Split([char]13)  
157:       foreach ($Item in $SplitInfo) {  
158:            $Property = $Item.Split(":").Trim()  
159:            switch ($Property[0]) {  
160:                 "InternalName" {  
161:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name InternalName -Value $Property[1]  
162:                 }  
163:                 "OriginalFileName" {  
164:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name OriginalFileName -Value $Property[1]  
165:                 }  
166:                 "Product" {  
167:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name Product -Value $Property[1]  
168:                 }  
169:                 "Debug" {  
170:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name Debug -Value $Property[1]  
171:                 }  
172:                 "Patched" {  
173:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name Patched -Value $Property[1]  
174:                 }  
175:                 "PreRelease" {  
176:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name PreRelease -Value $Property[1]  
177:                 }  
178:                 "PrivateBuild" {  
179:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name PrivateBuild -Value $Property[1]  
180:                 }  
181:                 "SpecialBuild" {  
182:                      $MetaDataObject | Add-Member -MemberType NoteProperty -Name SpecialBuild -Value $Property[1]  
183:                 }  
184:            }  
185:       }  
186:         
187:       #Check if file is read-only  
188:       $ReadOnly = (Get-ChildItem $FileName) | Select-Object IsReadOnly  
189:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name ReadOnly -Value $ReadOnly.IsReadOnly  
190:       #Get digital file signature information  
191:       $DigitalSignature = get-authenticodesignature -filepath $FileName  
192:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateSubject -Value $DigitalSignature.SignerCertificate.Subject  
193:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateIssuer -Value $DigitalSignature.SignerCertificate.Issuer  
194:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateSerialNumber -Value $DigitalSignature.SignerCertificate.SerialNumber  
195:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateNotBefore -Value $DigitalSignature.SignerCertificate.NotBefore  
196:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateNotAfter -Value $DigitalSignature.SignerCertificate.NotAfter  
197:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateThumbprint -Value $DigitalSignature.SignerCertificate.Thumbprint  
198:       $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureStatus -Value $DigitalSignature.Status  
199:       If (($MetaDataObject -ne "") -and ($MetaDataObject -ne $null)) {  
200:            Write-Host "Success" -ForegroundColor Yellow  
201:       } else {  
202:            Write-Host "Failed" -ForegroundColor Red  
203:       }  
204:       Return $MetaDataObject  
205:  }  
206:    
207:  function Invoke-EXE {  
208:  <#  
209:       .SYNOPSIS  
210:            Install or Uninstall Executable  
211:         
212:       .DESCRIPTION  
213:            A detailed description of the Invoke-EXE function.  
214:         
215:       .PARAMETER InstallerMetaData  
216:            The metadata extracted from the executable  
217:         
218:       .PARAMETER Install  
219:            Specify to Install the application  
220:         
221:       .PARAMETER Uninstall  
222:            Specify to uninstall the application  
223:         
224:       .PARAMETER Executable  
225:            The installation file for installing the application  
226:         
227:       .PARAMETER Switches  
228:            Switches to control the executable file  
229:         
230:       .PARAMETER DisplayName  
231:            Name to be displayed while installing or uninstalling the application  
232:         
233:       .EXAMPLE  
234:            PS C:\> Invoke-EXE  
235:         
236:       .NOTES  
237:            Additional information about the function.  
238:  #>  
239:         
240:       [CmdletBinding()][OutputType([boolean])]  
241:       param  
242:       (  
243:            [object]  
244:            $InstallerMetaData,  
245:            [switch]  
246:            $Install,  
247:            [switch]  
248:            $Uninstall,  
249:            [ValidateNotNullOrEmpty()][string]  
250:            $Executable,  
251:            [string]  
252:            $Switches,  
253:            [string]  
254:            $DisplayName  
255:       )  
256:         
257:       If ($Install.IsPresent) {  
258:            Write-Host "Initiating Installation of"$DisplayName"....." -NoNewline  
259:            $File = $env:windir + "\ccmsetup\ccmsetup.exe"  
260:            $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode  
261:            If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
262:                 Write-Host "Success" -ForegroundColor Yellow  
263:                 If ((Test-Path $File) -eq $true) {  
264:                      Wait-ProcessEnd -ProcessName ccmsetup  
265:                 } else {  
266:                      Write-Host "Failed" -ForegroundColor Red  
267:                      $Failed = $true  
268:                 }  
269:            } else {  
270:                 Write-Host "Failed with error"$ErrCode -ForegroundColor Red  
271:            }  
272:       } elseif ($Uninstall.IsPresent) {  
273:            Write-Host "Uninstalling"$DisplayName"....." -NoNewline  
274:            $File = $env:windir + "\ccmsetup\ccmsetup.exe"  
275:            If ((Test-Path $File) -eq $true) {  
276:                 $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode  
277:                 If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
278:                      Write-Host "Success" -ForegroundColor Yellow  
279:                      If ((Test-Path $File) -eq $true) {  
280:                           Wait-ProcessEnd -ProcessName ccmsetup  
281:                      }  
282:                 } else {  
283:                      $Failed = $true  
284:                      Write-Host "Failed with error"$ErrCode -ForegroundColor Red  
285:                 }  
286:            } else {  
287:                 Write-Host "Not Present" -ForegroundColor Green  
288:            }  
289:       }  
290:       If ($Failed -eq $true) {  
291:            Return $false  
292:       } else {  
293:            Return $true  
294:       }  
295:  }  
296:    
297:  function Remove-File {  
298:  <#  
299:       .SYNOPSIS  
300:            Delete a file with verification  
301:         
302:       .DESCRIPTION  
303:            Delete a file and verify the file no longer exists  
304:         
305:       .PARAMETER Filename  
306:            Name of the file to delete  
307:         
308:       .EXAMPLE  
309:            PS C:\> Remove-File -Filename 'Value1'  
310:         
311:       .NOTES  
312:            Additional information about the function.  
313:  #>  
314:         
315:       [CmdletBinding()][OutputType([boolean])]  
316:       param  
317:       (  
318:            [ValidateNotNullOrEmpty()][string]  
319:            $Filename  
320:       )  
321:         
322:       If ((Test-Path $Filename) -eq $false) {  
323:            Write-Host $Filename" already deleted"  
324:       } else {  
325:            $File = Get-Item $Filename -Force  
326:            Write-Host "Deleting"$File.Name"....." -NoNewline  
327:            If (Test-Path $File) {  
328:                 Remove-Item $File -Force -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Out-Null  
329:                 If ((Test-Path $Filename) -eq $False) {  
330:                      Write-Host "Success" -ForegroundColor Yellow  
331:                 } else {  
332:                      $Failed = $true  
333:                      Write-Host "Failed" -ForegroundColor Red  
334:                 }  
335:            } else {  
336:                 Write-Host "Not Present" -ForegroundColor Green  
337:            }  
338:       }  
339:       If ($Failed -eq $true) {  
340:            Return $false  
341:       } else {  
342:            Return $true  
343:       }  
344:  }  
345:    
346:  function Remove-RegistryKey {  
347:  <#  
348:       .SYNOPSIS  
349:            Delete registry key  
350:         
351:       .DESCRIPTION  
352:            Delete a registry key. If recurse is selected, all subkeys and values are deleted  
353:         
354:       .PARAMETER RegistryKey  
355:            Registry key to delete  
356:         
357:       .PARAMETER Recurse  
358:            Include all subkeys when deleting the registry key  
359:         
360:       .EXAMPLE  
361:            PS C:\> Remove-RegistryKey -RegistryKey 'Value1'  
362:         
363:       .NOTES  
364:            Additional information about the function.  
365:  #>  
366:         
367:       [CmdletBinding()]  
368:       param  
369:       (  
370:            [ValidateNotNullOrEmpty()][string]  
371:            $RegistryKey,  
372:            [switch]  
373:            $Recurse  
374:       )  
375:         
376:       $RegKey = "Registry::" + $RegistryKey  
377:       If ((Test-Path $RegKey) -eq $false) {  
378:            Write-Host $RegKey" already deleted"  
379:       } else {  
380:            $RegKeyItem = Get-Item $RegKey  
381:            If ($Recurse.IsPresent) {  
382:                 Write-Host "Recursive Deletion of"$RegKeyItem.PSChildName"....." -NoNewline  
383:                 Remove-Item $RegKey -Recurse -Force | Out-Null  
384:            } else {  
385:                 Write-Host "Deleting"$RegKeyItem.PSChildName"....." -NoNewline  
386:                 Remove-Item $RegKey -Force | Out-Null  
387:            }  
388:            If ((Test-Path $RegKey) -eq $false) {  
389:                 Write-Host "Success" -ForegroundColor Yellow  
390:            } else {  
391:                 $Failed = $true  
392:                 Write-Host "Failed" -ForegroundColor Red  
393:            }  
394:       }  
395:       If ($Failed -eq $true) {  
396:            Return $false  
397:       } else {  
398:            Return $true  
399:       }  
400:  }  
401:    
402:  function Set-ConsoleTitle {  
403:  <#  
404:       .SYNOPSIS  
405:            Console Title  
406:         
407:       .DESCRIPTION  
408:            Sets the title of the PowerShell Console  
409:         
410:       .PARAMETER ConsoleTitle  
411:            Title of the PowerShell Console  
412:         
413:       .NOTES  
414:            Additional information about the function.  
415:  #>  
416:         
417:       [CmdletBinding()]  
418:       param  
419:       (  
420:            [Parameter(Mandatory = $true)][String]  
421:            $ConsoleTitle  
422:       )  
423:         
424:       $host.ui.RawUI.WindowTitle = $ConsoleTitle  
425:  }  
426:    
427:  function Suspend-Service {  
428:  <#  
429:       .SYNOPSIS  
430:            Stop specified service  
431:         
432:       .DESCRIPTION  
433:            Stop a specified service and verify it is stopped  
434:         
435:       .PARAMETER Service  
436:            Name of the service  
437:         
438:       .EXAMPLE  
439:            PS C:\> Suspend-Service -Service 'Value1'  
440:         
441:       .NOTES  
442:            Additional information about the function.  
443:  #>  
444:         
445:       [CmdletBinding()][OutputType([boolean])]  
446:       param  
447:       (  
448:            [ValidateNotNullOrEmpty()][string]  
449:            $Service  
450:       )  
451:         
452:       $ServiceStatus = Get-Service $Service -WarningAction SilentlyContinue -ErrorAction SilentlyContinue  
453:       If ($ServiceStatus -ne $null) {  
454:            Write-Host "Stopping"$ServiceStatus.DisplayName"....." -NoNewline  
455:            If ($ServiceStatus.Status -ne 'Stopped') {  
456:                 Stop-Service -Name $Service -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -Force  
457:                 $ServiceStatus = Get-Service $Service -WarningAction SilentlyContinue -ErrorAction SilentlyContinue  
458:                 If ($ServiceStatus.Status -eq 'Stopped') {  
459:                      Write-Host "Success" -ForegroundColor Yellow  
460:                 } else {  
461:                      $Failed = $true  
462:                      Write-Host "Failed" -ForegroundColor Red  
463:                 }  
464:            } else {  
465:                 Write-Host "Service already stopped" -ForegroundColor Yellow  
466:            }  
467:       } else {  
468:            Write-Host $Service"service does not exist"  
469:       }  
470:       If ($Failed -eq $true) {  
471:            Return $false  
472:       } else {  
473:            Return $true  
474:       }  
475:  }  
476:    
477:  function Wait-ProcessEnd {  
478:  <#  
479:       .SYNOPSIS  
480:            Wait for a process to end  
481:         
482:       .DESCRIPTION  
483:            Pause the script until a process no longer exists  
484:         
485:       .PARAMETER ProcessName  
486:            Name of the process  
487:         
488:       .EXAMPLE  
489:                      PS C:\> Wait-ProcessEnd -ProcessName 'Value1'  
490:         
491:       .NOTES  
492:            Additional information about the function.  
493:  #>  
494:         
495:       [CmdletBinding()]  
496:       param  
497:       (  
498:            [ValidateNotNullOrEmpty()][string]  
499:            $ProcessName  
500:       )  
501:         
502:       $Process = Get-Process $ProcessName -ErrorAction SilentlyContinue  
503:       $Process = $Process | Where-Object { $_.ProcessName -eq $ProcessName }  
504:       Write-Host "Waiting for"$Process.Product"to complete....." -NoNewline  
505:       If ($Process -ne $null) {  
506:            Do {  
507:                 Start-Sleep -Seconds 2  
508:                 $Process = Get-Process $ProcessName -ErrorAction SilentlyContinue  
509:                 $Process = $Process | Where-Object { $_.ProcessName -eq $ProcessName }  
510:            }  
511:            While ($Process -ne $null)  
512:            Write-Host "Completed" -ForegroundColor Yellow  
513:       } else {  
514:            Write-Host "Process already completed" -ForegroundColor Yellow  
515:       }  
516:  }  
517:    
518:  cls  
519:  #Set the name of the powershell console  
520:  Set-ConsoleTitle -ConsoleTitle "SCCM Client"  
521:  #Skip over if the install directory and installer file are not defined  
522:  If ($ClientInstallationDirectory -ne $null) {  
523:       If ($ClientInstallationFile -ne $null) {  
524:            If ($ClientInstallationDirectory[$ClientInstallationDirectory.Length - 1] -ne '\') {  
525:                 $ClientInstallationDirectory += '\'  
526:            }  
527:            #Set the location and filename of the SCCM client installer  
528:            $File = $ClientInstallationDirectory + $ClientInstallationFile  
529:            #Get metadata from the SCCM client installer file  
530:            $FileMetaData = Get-MetaData -FileName $File  
531:       }  
532:  }  
533:  #Install parameter is defined  
534:  If ($Install.IsPresent) {  
535:       #Uninstall the SCCM client  
536:       $Parameters = "/uninstall"  
537:       $InstallStatus = Invoke-EXE -Uninstall -DisplayName $FileMetaData.Product -Executable $File -Switches $Parameters  
538:       If ($InstallStatus = $false) {  
539:            $Failed = $true  
540:       }  
541:       #Install the SCCM client  
542:       $Parameters = ""  
543:       If (($ManagementPoint -ne $null) -and ($ManagementPoint -ne "")) {  
544:            $Parameters += "/mp:" + $ManagementPoint  
545:       }  
546:       If (($SMSSiteCode -ne $null) -and ($SMSSiteCode -ne "")) {  
547:            If ($Parameters -ne "") {  
548:                 $Parameters += [char]32  
549:            }  
550:            $Parameters += "SMSSITECODE=" + $SMSSiteCode  
551:       }  
552:       If ($UsePKICert.IsPresent) {  
553:            If ($Parameters -ne "") {  
554:                 $Parameters += [char]32  
555:            }  
556:            $Parameters += "/UsePKICert"  
557:       }  
558:       If ($NOCRLCheck.IsPresent) {  
559:            If ($Parameters -ne "") {  
560:                 $Parameters += [char]32  
561:            }  
562:            $Parameters += "/NOCRLCheck"  
563:       }  
564:       If (($Source -ne $null) -and ($Source -ne "")) {  
565:            If ($Parameters -ne "") {  
566:                 $Parameters += [char]32  
567:            }  
568:            $Parameters += "/source:" + [char]34 + $Source + [char]34  
569:       }  
570:       $InstallStatus = Invoke-EXE -Install -DisplayName $FileMetaData.Product -Executable $File -Switches $Parameters  
571:       If ($InstallStatus -eq $false) {  
572:            $Failed = $true  
573:       }  
574:       #Uninstall parameter is defined  
575:  } elseif ($Uninstall.IsPresent) {  
576:       #Uninstall the SCCM client  
577:       $Parameters = "/Uninstall"  
578:       $InstallStatus = Invoke-EXE -Uninstall -DisplayName $FileMetaData.Product -Executable $File -Switches $Parameters  
579:       If ($InstallStatus -eq $false) {  
580:            $Failed = $true  
581:       }  
582:  }  
583:  #Build parameter is defined  
584:  If ($Build.IsPresent) {  
585:       #Stop the configuration manager client service  
586:       $InstallStatus = Suspend-Service -Service ccmexec  
587:       If ($InstallStatus -eq $false) {  
588:            $Failed = $true  
589:       }  
590:       #Delete the smscfg.ini file  
591:       $InstallStatus = Remove-File -Filename $env:windir"\smscfg.ini"  
592:       If ($InstallStatus -eq $false) {  
593:            $Failed = $true  
594:       }  
595:       #Delete the SCCM certificates from the registry  
596:       $InstallStatus = Remove-RegistryKey -RegistryKey "HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates\SMS\Certificates" -Recurse  
597:       If ($InstallStatus -eq $false) {  
598:            $Failed = $true  
599:       }  
600:  }  
601:  If ($Failed -eq $true) {  
602:       $wshell = New-Object -ComObject Wscript.Shell  
603:       $wshell.Popup("Installation Failed", 0, "Installation Failed", 0x0)  
604:       Exit 1  
605:  } else {  
606:       Exit 0  
607:  }  
608: