11 November 2016

SCCM: Local Administrators Reporting

Here is a script that will gather a list of local administrators on a machine. The script can report the list to SCCM by writing the list to a WMI entry. It can also write the list to a text file at a specified location for admins that do not have SCCM. The text file is named <%COMPUTERNAME%>.txt. You can do both SCCM reporting and text file reporting if desired.

To implement this into SCCM, run the script once on a machine with the following command line:

powershell.exe -file LocalAdmins.ps1 -SCCMReporting

Once this has executed, do the following: 
  1. Open the SCCM console
  2. Click on Administration
  3. Click Client Settings
  4. Right-click Default Client Settings
  5. Left-click Properties
  6. Click Hardware Inventory
  7. Click Set Classes
  8. Click Add
  9. Click Connect
  10. Enter the computer name of the system you ran the script on
  11. Check Recursive
  12. Check Credentials required
  13. Enter the domain\username for user name
  14. Enter the associated password
  15. Click Connect
  16. Once the list of classes appears, click on Class Name to sort the classes
  17. Scroll down to find Local_Administrators and check the box to the left
  18. Click OK
  19. Click OK
  20. Click OK
  21. Now go back to the machine you ran the script on and run a hardware inventory to send the data up to SCCM. It will take a few minutes until the data appears in SCCM

The next step is to setup the script to execute through SCCM as a package. The script will need to be executed on a routine basis if you want it to be reported regularly to SCCM. As a package, the following pictures show how I have it configured in SCCM.



Finally, you will want to be able to look at the results. You can create a query to show the systems that have reported users in the local administrators group. Here is the WQL I use:


 select distinct SMS_R_System.Name, SMS_G_System_LOCAL_ADMINISTRATORS.Domain, SMS_G_System_LOCAL_ADMINISTRATORS.User from SMS_R_System inner join SMS_G_System_LOCAL_ADMINISTRATORS on SMS_G_System_LOCAL_ADMINISTRATORS.ResourceID = SMS_R_System.ResourceId order by SMS_R_System.Name, SMS_G_System_LOCAL_ADMINISTRATORS.Domain, SMS_G_System_LOCAL_ADMINISTRATORS.User  

You can download the SCCM WQL Query from here.

You can download the script from my GitHub repository located here


LocalAdmins.ps1

 <#  
      .SYNOPSIS  
           Report Local administrators  
        
      .DESCRIPTION  
           Report a list of local administrators on machines to a designated text file, screen, and/or to SCCM via a WMI entry.  
        
      .PARAMETER MemberExclusionsFile  
           Text file containing a list of users to exclude  
        
      .PARAMETER OutputFile  
           Specifies if the output is to be written to a text file. The OutputFileLocation parameter also needs to be populated with the location to write the text file to.  
        
      .PARAMETER OutputFileLocation  
           Location where to write the output text files  
        
      .PARAMETER SCCMReporting  
           Report results to SCCM  
        
      .PARAMETER SystemExclusionsFile  
           Text file containing a list of systems to not generate a report on  
        
      .EXAMPLE  
           Get a list of local admins without reporting to SCCM or writing output to text file  
           powershell.exe -file LocalAdmins.ps1  
             
           Get a list of local admins and report to SCCM  
           powershell.exe -file LocalAdmins.ps1 -SCCMReporting  
             
           Get a list of local admins and write report to a text file at a specified location  
           powershell.exe -file LocalAdmins.ps1 -OutputFile  
        
      .NOTES  
           ===========================================================================  
           Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.129  
           Created on:       11/9/2016 12:47 PM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:        LocalAdministrators.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [string]  
      $MemberExclusionsFile = 'MemberExclusions.txt',  
      [switch]  
      $OutputFile,  
      [string]  
      $OutputFileLocation = '',  
      [switch]  
      $SCCMReporting,  
      [string]  
      $SystemExclusionsFile = 'SystemExclusions.txt'  
 )  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function Invoke-SCCMHardwareInventory {  
 <#  
      .SYNOPSIS  
           Initiate a Hardware Inventory  
        
      .DESCRIPTION  
           This will initiate a hardware inventory that does not include a full hardware inventory. This is enought to collect the WMI data.  
        
      .EXAMPLE  
                     PS C:\> Invoke-SCCMHardwareInventory  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      $ComputerName = $env:COMPUTERNAME  
      $SMSCli = [wmiclass] "\\$ComputerName\root\ccm:SMS_Client"  
      $SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}") | Out-Null  
 }  
   
 function New-WMIClass {  
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]  
           $Class  
      )  
        
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If ($WMITest -ne $null) {  
           $Output = "Deleting " + $Class + " WMI class....."  
           Remove-WmiObject $Class  
           $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
           If ($WMITest -eq $null) {  
                $Output += "success"  
           } else {  
                $Output += "Failed"  
                Exit 1  
           }  
           Write-Output $Output  
      }  
      $Output = "Creating " + $Class + " WMI class....."  
      $newClass = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);  
      $newClass["__CLASS"] = $Class;  
      $newClass.Qualifiers.Add("Static", $true)  
      $newClass.Properties.Add("Domain", [System.Management.CimType]::String, $false)  
      $newClass.Properties["Domain"].Qualifiers.Add("key", $true)  
      $newClass.Properties["Domain"].Qualifiers.Add("read", $true)  
      $newClass.Properties.Add("User", [System.Management.CimType]::String, $false)  
      $newClass.Properties["User"].Qualifiers.Add("key", $false)  
      $newClass.Properties["User"].Qualifiers.Add("read", $true)  
      $newClass.Put() | Out-Null  
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If ($WMITest -eq $null) {  
           $Output += "success"  
      } else {  
           $Output += "Failed"  
           Exit 1  
      }  
      Write-Output $Output  
 }  
   
 function New-WMIInstance {  
 <#  
      .SYNOPSIS  
           Write new instance  
        
      .DESCRIPTION  
           A detailed description of the New-WMIInstance function.  
        
      .PARAMETER MappedDrives  
           List of mapped drives  
        
      .PARAMETER Class  
           A description of the Class parameter.  
        
      .EXAMPLE  
           PS C:\> New-WMIInstance  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][array]  
           $LocalAdministrators,  
           [string]  
           $Class  
      )  
        
      foreach ($LocalAdministrator in $LocalAdministrators) {  
           $Output = "Writing" + [char]32 +$LocalAdministrator.User + [char]32 + "instance to" + [char]32 + $Class + [char]32 + "class....."  
           $Return = Set-WmiInstance -Class $Class -Arguments @{ Domain = $LocalAdministrator.Domain; User = $LocalAdministrator.User }  
           If ($Return -like "*" + $LocalAdministrator.User + "*") {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
           }  
           Write-Output $Output  
      }  
 }  
   
 cls  
 #Get the path this script is being executed from  
 $RelativePath = Get-RelativePath  
 #Name of the computer this script is being executed on  
 $ComputerName = $Env:COMPUTERNAME  
 #Read the list of systems to exclude from reporting  
 $File = $RelativePath + $SystemExclusionsFile  
 $SystemExclusions = Get-Content $File  
 If ($SystemExclusions -notcontains $Env:COMPUTERNAME) {  
      #Get list of users to exclude from reporting  
      $File = $RelativePath + $MemberExclusionsFile  
      $MemberExclusions = Get-Content $File  
      #Get list of local administrators while excluding specified members  
      $Members = net localgroup administrators | Where-Object { $_ -AND $_ -notmatch "command completed successfully" } | select -skip 4 | Where-Object { $MemberExclusions -notcontains $_ }  
      $LocalAdmins = @()  
      foreach ($Member in $Members) {  
           #Create new object  
           $Admin = New-Object -TypeName System.Management.Automation.PSObject  
           $Member = $Member.Split("\")  
           If ($Member.length -gt 1) {  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name Domain -Value $Member[0].Trim()  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name User -Value $Member[1].Trim()  
           } else {  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name Domain -Value ""  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name User -Value $Member.Trim()  
           }  
           $LocalAdmins += $Admin  
      }  
 }  
 #Report output to WMI which will report up to SCCM  
 If ($SCCMReporting.IsPresent) {  
      New-WMIClass -Class "Local_Administrators"  
      New-WMIInstance -Class "Local_Administrators" -LocalAdministrators $LocalAdmins  
      #Report WMI entry to SCCM  
      Invoke-SCCMHardwareInventory  
 }  
 If ($OutputFile.IsPresent) {  
      If ($OutputFileLocation[$OutputFileLocation.Length - 1] -ne "\") {  
           $File = $OutputFileLocation + "\" + $ComputerName + ".log"  
      } else {  
           $File = $OutputFileLocation + $ComputerName + ".log"  
      }  
      #Delete old log file if it exists  
      $Output = "Deleting $ComputerName.log....."  
      If ((Test-Path $File) -eq $true) {  
           Remove-Item -Path $File -Force  
      }  
      If ((Test-Path $File) -eq $false) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
      $Output = "Writing local admins to $ComputerName.log....."  
      $LocalAdmins | Out-File $File  
      If ((Test-Path $File) -eq $true) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
 #Display list of local administrators to screen  
 $LocalAdmins  
   

26 October 2016

SCCM Mapped Drives Report

Recently, we wanted to start keeping track of users with mapped drives due to cryptolocker vulnerabilities. There are a few applications we have that require mapped drives, so we have certain users with them. Once again, I must say thank you to Sapien Technology for PowerShell Studio! That tool makes writing these PowerShell scripts a breeze.

This script will scan all user profiles on a machine and report users with mapped drives. This is done by parsing through the HKU registries. It has been written so that you can either have the script write the report to a text file if you do not have SCCM and/or it can write it to WMI so that SCCM can read the results. I have also included a UNCPathExclusionsFile parameter that allows you to create a text file that resides in the same directory as the script. It contains a list of UNC paths that you do not want the script to report. I recommend pre-populating the values of the $TextFileLocation and $UNCPathExclusionsFile parameters within the script. That just leaves the $OutputFile and $SCCMReporting left to specify at the command line.

If you are wanting this to write the results to SCCM, here is what you need to do. First, SCCM needs to know what to look for in order to report on it. This script will use WMI to report that data to SCCM. The first thing is to execute the script locally on any PC. Run it using the following command line: powershell.exe -file MappedDriveReport.ps1 -SCCMReporting



That command line will execute the script to scan for mapped drives write the results to WMI and then initiate a hardware inventory. Because the new WMI entry has not been added to SCCM, it will not be reported yet. Now that you have executed the script on the local machine, do the following:

  1. Go into SCCM--->Administration Tab--->Client Settings---> Default Client Settings--->Hardware Inventory--->Set Classes.
  2. Click Add--->Connect.
  3. Enter the computer name of the system you ran the script on, check recursive, check Credentials required (Computer is not local)---> <domain>\<username> in the username field, and finally the password for the associated username. 
  4. Click Connect
  5. Click on the Class Name tab to sort by class name
  6. Scroll down to find MappedDrives and check the box
  7. Click OK

You have now added the WMI class to SCCM for it to grab the data from the PCs and report it back to SCCM. 


To get the systems to report the data back to SCCM, you will need to setup a package, not an application, in SCCM to deploy out to the systems. I have the package setup to re-run once a week at 12:00 pm on Wednesdays so that I can get the most users to report back. More users are online at that time here than any of the other days.

If you read the .Example in the documentation portion of the script, you will see two examples on how to execute the script.

I have also included a hardware inventory within the script so the data will be reported back to SCCM right after the script is executed.

In order to view the data in SCCM, you can do the following using the Resource Explorer:

  1. Right-click on a machine in the Assets and Compliance--->Devices
  2. Click Start--->Resource Explorer
  3. Click the plus beside Hardware
  4. If a system had mapped drives, then there will be a mapped drives field, otherwise it does not exist.
You can also use the queries to report systems with mapped drives. Here is the query I use:
select distinct SMS_G_System_MAPPEDDRIVES.user, SMS_G_System_MAPPEDDRIVES.Letter, SMS_G_System_MAPPEDDRIVES.Path from  SMS_R_System inner join SMS_G_System_MAPPEDDRIVES on SMS_G_System_MAPPEDDRIVES.ResourceID = SMS_R_System.ResourceId order by SMS_G_System_MAPPEDDRIVES.user

If you do not have SCCM and need a report, you can use the -OutputFile to have it write the results to a text file at the specified location defined in the $TextFileLocation parameter. 


The script is available on my GitHub site located here.

MappedDriveReport.ps1
1:  <#  
2:       .SYNOPSIS  
3:            Get List of Mapped Drives  
4:         
5:       .DESCRIPTION  
6:            Scans each profile for a list of mapped drives. It will generate a screen report and can also write the output to the WMI for reporting to SCCM.  
7:         
8:       .PARAMETER OutputFile  
9:            Specifies if the output is to be written to a text file. The TextFileLocation parameter also needs to be populated with the location to write the text file to.  
10:         
11:       .PARAMETER TextFileLocation  
12:            Location where to write the text file to  
13:         
14:       .PARAMETER UNCPathExclusionsFile  
15:            Text file containing a list of UNC paths to exclude from reporting.  
16:         
17:       .PARAMETER SCCMReporting  
18:            Specifies to write the data to WMI so that SCCM can pickup the data.  
19:         
20:       .PARAMETER TextFileName  
21:            Write output to a text file  
22:    
23:       .EXAMPLE  
24:            Execute and write output to the reporting file location and also write output to WMI for reporting to SCCM. -TextFileLocation parameter is prepopulated below.  
25:                 powershell.exe -file MappedDriveReport.ps1 -OutputFile -SCCMReporting  
26:    
27:            Execute and write output to WMI to report to SCCM.  
28:                 powershell.exe -file MappedDriveReport.ps1 -SCCMReporting  
29:         
30:       .NOTES  
31:            ===========================================================================  
32:            Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.128  
33:            Created on:       10/7/2016 10:57 AM  
34:            Created by:       Mick Pletcher  
35:            Organization:  
36:            Filename:          MappedDriveReport.ps1  
37:            ===========================================================================  
38:  #>  
39:  [CmdletBinding()]  
40:  param  
41:  (  
42:       [switch]  
43:       $OutputFile,  
44:       [string]  
45:       $TextFileLocation = '\\drfs1\DesktopApplications\ProductionApplications\Waller\MappedDrivesReport\Reports',  
46:       [string]  
47:       $UNCPathExclusionsFile = "\\drfs1\DesktopApplications\ProductionApplications\Waller\MappedDrivesReport\UNCPathExclusions.txt",  
48:       [switch]  
49:       $SCCMReporting  
50:  )  
51:    
52:  function Get-CurrentDate {  
53:  <#  
54:       .SYNOPSIS   
55:            Get the current date and return formatted value   
56:    
57:       .DESCRIPTION   
58:            Return the current date in the following format: mm-dd-yyyy   
59:    
60:       .NOTES   
61:            Additional information about the function.   
62:  #>  
63:         
64:       [CmdletBinding()][OutputType([string])]  
65:       param ()  
66:    
67:       $CurrentDate = Get-Date  
68:       $CurrentDate = $CurrentDate.ToShortDateString()  
69:       $CurrentDate = $CurrentDate -replace "/", "-"  
70:       If ($CurrentDate[2] -ne "-") {  
71:            $CurrentDate = $CurrentDate.Insert(0, "0")  
72:       }  
73:       If ($CurrentDate[5] -ne "-") {  
74:            $CurrentDate = $CurrentDate.Insert(3, "0")  
75:       }  
76:       Return $CurrentDate  
77:  }  
78:    
79:  function Get-MappedDrives {  
80:  <#  
81:       .SYNOPSIS  
82:            Get list of Mapped Drives  
83:         
84:       .DESCRIPTION  
85:            Retrieve a list of mapped drives for each user that has logged onto the machine.  
86:         
87:       .EXAMPLE  
88:            PS C:\> Get-MappedDrives  
89:         
90:       .NOTES  
91:            Additional information about the function.  
92:  #>  
93:         
94:       [CmdletBinding()][OutputType([array])]  
95:         
96:       #Get UNC Exclusions from UNCPathExclusions.txt file  
97:       $UNCExclusions = Get-Content $UNCPathExclusionsFile -Force  
98:       #Get HKEY_Users Registry Keys  
99:       [array]$UserSIDS = (Get-ChildItem -Path REGISTRY::HKEY_Users | Where-Object { ($_ -notlike "*Classes*") -and ($_ -like "*S-1-5-21*") }).Name  
100:       #Get Profiles from HKLM  
101:       [array]$ProfileList = (Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_ -like "*S-1-5-21*" }).Name  
102:       $UserMappedDrives = @()  
103:       #Iterate through each HKEY_USERS profile  
104:       foreach ($UserSID in $UserSIDS) {  
105:            #GET SID only  
106:            [string]$UserSID = $UserSID.Split("\")[1].Trim()  
107:            #Find the userprofile that matches the HKEY_USERS  
108:            [string]$UserPROFILE = $ProfileList | Where-Object { $_ -like "*" + $UserSID + "*" }  
109:            #Get the username associated with the SID  
110:            $Username = ((Get-ItemProperty -Path REGISTRY::$UserPROFILE).ProfileImagePath).Split("\")[2].trim()  
111:            #Define registry path to mapped drives  
112:            [string]$MappedDrives = "HKEY_USERS\" + $UserSID + "\Network"  
113:            #Get list of mapped drives  
114:            [array]$MappedDrives = (Get-ChildItem REGISTRY::$MappedDrives | Select-Object name).name  
115:            foreach ($MappedDrive in $MappedDrives) {  
116:                 $DriveLetter = (Get-ItemProperty -Path REGISTRY::$MappedDrive | select PSChildName).PSChildName  
117:                 $DrivePath = (Get-ItemProperty -Path REGISTRY::$MappedDrive | select RemotePath).RemotePath  
118:                 If ($DrivePath -notin $UNCExclusions) {  
119:                      $Drives = New-Object System.Management.Automation.PSObject  
120:                      $Drives | Add-Member -MemberType NoteProperty -Name ComputerName -Value $env:COMPUTERNAME  
121:                      $Drives | Add-Member -MemberType NoteProperty -Name Username -Value $Username  
122:                      $Drives | Add-Member -MemberType NoteProperty -Name DriveLetter -Value $DriveLetter  
123:                      $Drives | Add-Member -MemberType NoteProperty -Name DrivePath -Value $DrivePath  
124:                      $UserMappedDrives += $Drives  
125:                 }  
126:            }  
127:       }  
128:       Return $UserMappedDrives  
129:  }  
130:    
131:  function Get-RelativePath {  
132:  <#  
133:       .SYNOPSIS  
134:            Get the relative path  
135:         
136:       .DESCRIPTION  
137:            Returns the location of the currently running PowerShell script  
138:         
139:       .NOTES  
140:            Additional information about the function.  
141:  #>  
142:         
143:       [CmdletBinding()][OutputType([string])]  
144:       param ()  
145:         
146:       $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
147:       Return $Path  
148:  }  
149:    
150:  function Invoke-SCCMHardwareInventory {  
151:  <#  
152:       .SYNOPSIS  
153:            Initiate a Hardware Inventory  
154:         
155:       .DESCRIPTION  
156:            This will initiate a hardware inventory that does not include a full hardware inventory. This is enought to collect the WMI data.  
157:         
158:       .EXAMPLE  
159:                      PS C:\> Invoke-SCCMHardwareInventory  
160:         
161:       .NOTES  
162:            Additional information about the function.  
163:  #>  
164:         
165:       [CmdletBinding()]  
166:       param ()  
167:         
168:       $ComputerName = $env:COMPUTERNAME  
169:       $SMSCli = [wmiclass] "\\$ComputerName\root\ccm:SMS_Client"  
170:       $SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}") | Out-Null  
171:  }  
172:    
173:  function New-WMIClass {  
174:       [CmdletBinding()]  
175:       param  
176:       (  
177:            [ValidateNotNullOrEmpty()][string]  
178:            $Class  
179:       )  
180:         
181:       $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
182:       If ($WMITest -ne $null) {  
183:            $Output = "Deleting " + $WMITest.__CLASS[0] + " WMI class....."  
184:            Remove-WmiObject $Class  
185:            $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
186:            If ($WMITest -eq $null) {  
187:                 $Output += "success"  
188:            } else {  
189:                 $Output += "Failed"  
190:                 Exit 1  
191:            }  
192:            Write-Output $Output  
193:       }  
194:       $Output = "Creating " + $Class + " WMI class....."  
195:       $newClass = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);  
196:       $newClass["__CLASS"] = $Class;  
197:       $newClass.Qualifiers.Add("Static", $true)  
198:       $newClass.Properties.Add("ComputerName", [System.Management.CimType]::String, $false)  
199:       $newClass.Properties["ComputerName"].Qualifiers.Add("key", $true)  
200:       $newClass.Properties["ComputerName"].Qualifiers.Add("read", $true)  
201:       $newClass.Properties.Add("DriveLetter", [System.Management.CimType]::String, $false)  
202:       $newClass.Properties["DriveLetter"].Qualifiers.Add("key", $false)  
203:       $newClass.Properties["DriveLetter"].Qualifiers.Add("read", $true)  
204:       $newClass.Properties.Add("DrivePath", [System.Management.CimType]::String, $false)  
205:       $newClass.Properties["DrivePath"].Qualifiers.Add("key", $false)  
206:       $newClass.Properties["DrivePath"].Qualifiers.Add("read", $true)  
207:       $newClass.Properties.Add("Username", [System.Management.CimType]::String, $false)  
208:       $newClass.Properties["Username"].Qualifiers.Add("key", $false)  
209:       $newClass.Properties["Username"].Qualifiers.Add("read", $true)  
210:       $newClass.Put() | Out-Null  
211:       $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
212:       If ($WMITest -eq $null) {  
213:            $Output += "success"  
214:       } else {  
215:            $Output += "Failed"  
216:            Exit 1  
217:       }  
218:       Write-Output $Output  
219:  }  
220:    
221:  function New-WMIInstance {  
222:  <#  
223:       .SYNOPSIS  
224:            Write new instance  
225:         
226:       .DESCRIPTION  
227:            A detailed description of the New-WMIInstance function.  
228:         
229:       .PARAMETER MappedDrives  
230:            List of mapped drives  
231:         
232:       .PARAMETER Class  
233:            A description of the Class parameter.  
234:         
235:       .EXAMPLE  
236:            PS C:\> New-WMIInstance  
237:         
238:       .NOTES  
239:            Additional information about the function.  
240:  #>  
241:         
242:       [CmdletBinding()]  
243:       param  
244:       (  
245:            [ValidateNotNullOrEmpty()][array]  
246:            $MappedDrives,  
247:            [string]  
248:            $Class  
249:       )  
250:         
251:       foreach ($MappedDrive in $MappedDrives) {  
252:            Set-WmiInstance -Class $Class -Arguments @{ ComputerName = $MappedDrive.ComputerName; DriveLetter = $MappedDrive.DriveLetter; DrivePath = $MappedDrive.DrivePath; Username = $MappedDrive.Username } | Out-Null  
253:       }  
254:  }  
255:    
256:  function Start-ConfigurationManagerClientScan {  
257:  <#   
258:       .SYNOPSIS   
259:            Initiate Configuration Manager Client Scan   
260:    
261:       .DESCRIPTION   
262:            This will initiate an SCCM action   
263:    
264:       .PARAMETER ScheduleID   
265:            GUID ID of the SCCM action   
266:    
267:       .NOTES   
268:            Additional information about the function.   
269:  #>  
270:    
271:       [CmdletBinding()]  
272:       param  
273:       (  
274:            [ValidateSet('00000000-0000-0000-0000-000000000121', '00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000010', '00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000021', '00000000-0000-0000-0000-000000000022', '00000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000031', '00000000-0000-0000-0000-000000000108', '00000000-0000-0000-0000-000000000113', '00000000-0000-0000-0000-000000000111', '00000000-0000-0000-0000-000000000026', '00000000-0000-0000-0000-000000000027', '00000000-0000-0000-0000-000000000032')]$ScheduleID  
275:       )  
276:    
277:       $WMIPath = "\\" + $env:COMPUTERNAME + "\root\ccm:SMS_Client"  
278:       $SMSwmi = [wmiclass]$WMIPath  
279:       $Action = [char]123 + $ScheduleID + [char]125  
280:       [Void]$SMSwmi.TriggerSchedule($Action)  
281:  }  
282:    
283:  cls  
284:  #Get list of mapped drives for each user  
285:  $UserMappedDrives = Get-MappedDrives  
286:  #Write output to a text file if -OutputFile is specified  
287:  If ($OutputFile.IsPresent) {  
288:       If (($TextFileLocation -ne $null) -and ($TextFileLocation -ne "")) {  
289:            #Add backslash (\) to the end of the TextFileLocation if it is not present  
290:            If ($TextFileLocation[$TextFileLocation.Length - 1] -ne "\") {  
291:                 $TextFileLocation += "\"  
292:            }  
293:            #Write list of mapped drives to the specified text file.  
294:            [string]$OutputFile = [string]$TextFileLocation + $env:COMPUTERNAME + ".txt"  
295:       } else {  
296:            #Get the relative path this script was executed from  
297:            $RelativePath = Get-RelativePath  
298:            $OutputFile = $RelativePath + $env:COMPUTERNAME + ".txt"  
299:       }  
300:       If ((Test-Path $OutputFile) -eq $true) {  
301:            Remove-Item $OutputFile -Force  
302:       }  
303:       If (($UserMappedDrives -ne $null) -and ($UserMappedDrives -ne "")) {  
304:            $UserMappedDrives | Format-Table -AutoSize | Out-File $OutputFile -Width 255  
305:       }  
306:  }  
307:  If ($SCCMReporting.IsPresent) {  
308:       #Create the new WMI class to write the output data to  
309:       New-WMIClass -Class "Mapped_Drives"  
310:       #Write the output data as an instance to the WMI class  
311:       If ($UserMappedDrives -ne $null) {  
312:            New-WMIInstance -MappedDrives $UserMappedDrives -Class "Mapped_Drives"  
313:       }  
314:       #Invoke a hardware inventory to send the data to SCCM  
315:       Invoke-SCCMHardwareInventory  
316:  }  
317:  #Display list of mapped drives for each user  
318:  $UserMappedDrives | Format-Table  
319:    

14 October 2016

PowerShell: Assign Email Address to Environment Variable

Recently, we encountered a special situation where we needed to have an environment variable that consisted of the user's email address. That prompted me to write the following script that will do just that. The script needs to be executed as the user. I set it up in the logon script and hid the PowerShell script. This script was executed on Windows 10 machines, so I cannot guarantee it will execute on other OS versions.

NOTE: This script requires RSAT to be installed. in order to utilize the ActiveDirectory module.


You can get the script from here.


1:  <#  
2:       .SYNOPSIS  
3:            Email Address Environment Variable  
4:         
5:       .DESCRIPTION  
6:            This script will assign the user's email address to the environment variable EmailAddress.  
7:         
8:       .NOTES  
9:            ===========================================================================  
10:            Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.128  
11:            Created on:        10/12/2016 1:14 PM  
12:            Created by:        Mick Pletcher  
13:            Organization:  
14:            Filename:          EmailEnvVariable.ps1
15:            ===========================================================================  
16:         
17:       .PARAMETER  
18:            A description of the parameter.  
19:  #>  
20:  [CmdletBinding()]  
21:  param ()  
22:    
23:  Import-Module ActiveDirectory  
24:  #Delete Environment Variable  
25:  [System.Environment]::SetEnvironmentVariable("EmailAddress", $null, 'User')  
26:  #Get the email address associated with the username  
27:  $EmailAddress = (Get-ADUser $env:USERNAME -Properties mail).mail  
28:  #Create a user based environment variable called email address  
29:  [System.Environment]::SetEnvironmentVariable("EmailAddress", $EmailAddress, 'User')  
30:    

04 October 2016

PowerShell: Generate Cached and Online Mode Exchange Report

Recently, the firm started converting outlook over to cached mode. As we are converting machines, we needed a report for how many of them are done and how many are still in Online mode.

I found the some great info here on how to get the information from the exchange logs. The first thing that needs to be done is to retrieve the exchange logs from the directory where they reside on each exchange server. If the exchange servers have more than one drive, make sure you are looking on the right one. The logs are located here: %PROGRAMFILES%\Microsoft\Exchange Server\V14\Logging\RPC Client Access. You will want to copy them into the same directory as this script resides. I appended all of the logs into a single log file. The script will filter out duplicates.

I have included examples in the script documentation on command line executions of this script.

I must say that Sapien's PowerShell Studio made writing this script a breeze.

Here is the link to the file on my GitHub site.


ExchangeModeReporting.ps1
 <#  
      .SYNOPSIS  
           Exchange Cached Mode Reporting  
        
      .DESCRIPTION  
           This script will read the RPC client access logs from the exchange server and generate a report showing if a user is cached or classic mode. You will need to retrieve the RPC logs from each exchange server. To do so, go to the drive on the exchange server that has exchange installed. The following directory contains the log you will need. Delete the Log Text, #Software, #Version, #Log-type, #Date, and #Fields lines from the file. That leaves the raw data. You can merge all of the logs into one file. This script will filter out repetitive entries.   
        
      .PARAMETER LogFile  
           Name of the log file that contains the exchange RPC logs  
        
      .PARAMETER Cached  
           Generate report of systems in cached mode  
        
      .PARAMETER Online  
           Generate report of all systems in online mode  
        
      .PARAMETER Full  
           Generate report showing both cached and online users  
        
      .PARAMETER OutputFile  
           Name of the file to write the output to. If left blank, no file is written to.  
        
      .EXAMPLE  
           Generate a list of all machines in cached mode  
                powershell.exe -file ExchangeModeReporting.ps1 -Cached -LogFile "Exchange.LOG"  
   
           Generate a list of all machines in cached mode and export list to a .CSV  
                powershell.exe -file ExchangeModeReporting.ps1 -Cached -LogFile "Exchange.LOG" -OutputFile "Report.csv"  
   
           Generate a list of all machines in Online mode  
                powershell.exe -file ExchangeModeReporting.ps1 -Online -LogFile "Exchange.LOG"  
   
           Generate a list of all machines in either cached or online mode  
                powershell.exe -file ExchangeModeReporting.ps1 -Full -LogFile "Exchange.LOG"  
   
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.128  
           Created on:       10/4/2016 10:13 AM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:         ExchangeModeReporting.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [ValidateNotNullOrEmpty()][string]  
      $LogFile,  
      [switch]  
      $Cached,  
      [switch]  
      $Online,  
      [switch]  
      $Full,  
      [string]  
      $OutputFile  
 )  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function Get-AccountInfo {  
 <#  
      .SYNOPSIS  
           Retrieve and format account infomation  
        
      .DESCRIPTION  
           This function will read the exchange log and extract the username and mailbox status, while putting the data into an object.  
        
      .EXAMPLE  
           PS C:\> Get-AccountInfo  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([array])]  
      param ()  
        
      $RelativePath = Get-RelativePath  
      $Logs = Get-Content $RelativePath$LogFile  
      $Systems = @()  
      foreach ($Log in $Logs) {  
           $System = New-Object System.Management.Automation.PSObject  
           $SplitLog = $Log.split(",")  
           $Username = ((($SplitLog | Where-Object { $_ -like "*cn=*" }).split("/") | Where-Object { ($_ -like "*cn=*") -and ($_ -notcontains "cn=Recipients") }).split("="))[1]  
           $Mode = $SplitLog | Where-Object { ($_ -contains "Classic") -or ($_ -contains "Cached") }  
           If ($Mode -eq "Classic") {  
                $Mode = "Online"  
           }  
           $System | Add-Member -type NoteProperty -Name Username -Value $Username  
           $System | Add-Member -type NoteProperty -Name Mode -Value $Mode  
           If ($Systems.Username -notcontains $Username) {  
                $Systems += $System  
           }  
      }  
      $Systems = $Systems | Sort-Object  
      Return $Systems  
 }  
   
 $Logs = Get-AccountInfo  
 if ($Cached.IsPresent) {  
      $Logs = $Logs | Where-Object { $_.Mode -eq "Cached" } | Sort-Object Username  
      $Logs | Format-Table  
 }  
 if ($Online.IsPresent) {  
      $Logs = $Logs | Where-Object { ($_.Mode -eq "Online") } | Sort-Object Username  
      $Logs | Format-Table  
 }  
 if ($Full.IsPresent) {  
      $Logs | Sort-Object Username  
 }  
 if (($OutputFile -ne $null) -and ($OutputFile -ne "")) {  
      $RelativePath = Get-RelativePath  
      $Logs | Sort-Object Username | Export-Csv $RelativePath$OutputFile -NoTypeInformation  
 }  

02 September 2016

PowerShell: Taking Ownership of Files and Folders

Recently, I ran into a situation where a deployment required taking ownership of a specific folder and all subfolders, including files. While formulating a method of doing this, I wanted to also make sure the script not only took ownership, but also verified it happened.

With the help of Sapien's PowerShell Studio, I wrote the following function that will do just that. It will take ownership using the credentials the script is executed under. It will then query the item(s) that is takes ownership of and verify the ownership of the item matches the ownership the script is being executed under. It will then return an success/failure screen output that is color coded yellow for success and red for failure. The script can be used for either single folders or files, or using the -Recurse tells it to change ownership for all subfolders and files.

You can download the script from here.

TakeOwnership.ps1

1:  <#  
2:       .SYNOPSIS  
3:            A brief description of the TakeOwnership.ps1 file.  
4:         
5:       .DESCRIPTION  
6:            This script will grant ownership of files to the credentials this script is being executed under.  
7:         
8:       .PARAMETER FilesFolders  
9:            Files and folders to change permissions on.  
10:    
11:       .EXAMPLE  
12:            powershell.exe -executionpolicy bypass -file TakeOwnership.ps1 -FilesFolders "c:\Users\Mick\AppData\Roaming\Microsoft\Windows"  
13:         
14:       .NOTES  
15:            ===========================================================================  
16:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127  
17:            Created on:       9/2/2016 9:49 AM  
18:            Created by:       Mick Pletcher  
19:            Organization:  
20:            Filename:         TakeOwnership.ps1  
21:            ===========================================================================  
22:  #>  
23:  [CmdletBinding()]  
24:  param  
25:  (  
26:       [ValidateNotNullOrEmpty()][string]  
27:       $FilesFolders  
28:  )  
29:    
30:  function Grant-FolderOwnership {  
31:  <#  
32:       .SYNOPSIS  
33:            Take FileFolder Ownership  
34:         
35:       .DESCRIPTION  
36:            Take ownership of the FileFolder  
37:         
38:       .PARAMETER FileFolder  
39:            File or FileFolder to take ownership of  
40:         
41:       .PARAMETER Recurse  
42:            Take ownership of all subfolders  
43:         
44:       .EXAMPLE  
45:            PS C:\> Grant-FolderOwnership -FileFolder 'Value1'  
46:         
47:       .NOTES  
48:            Additional information about the function.  
49:  #>  
50:         
51:       [CmdletBinding()]  
52:       param  
53:       (  
54:            [ValidateNotNullOrEmpty()][string]  
55:            $FileFolder,  
56:            [switch]  
57:            $Recurse  
58:       )  
59:         
60:       $Errors = $false  
61:       If ((Test-Path $FileFolder) -eq $true) {  
62:            $Output = "Taking ownership of " + $FileFolder + "....."  
63:            If ($Recurse.IsPresent) {  
64:                 #Take ownership of the top folder  
65:                 $Items = takeown.exe /F $FileFolder  
66:                 #Take ownership of all child folders and files  
67:                 $Items = Get-ChildItem $FileFolder -Recurse | ForEach-Object { takeown.exe /F $_.FullName }  
68:            } else {  
69:                 #Take ownership of the individual folder  
70:                 $Executable = takeown.exe /F $FileFolder  
71:            }  
72:       }  
73:       #Get the current user this script is being executed under  
74:       [string]$CurrentUser = [Environment]::UserDomainName + "\" + [Environment]::UserName  
75:       If ($Recurse.IsPresent) {  
76:            #Test if files are owned by the current user this script is being executed under  
77:            $Item = Get-Item $FileFolder | where-object { (get-acl $_.FullName).owner -ne $CurrentUser }  
78:            $Items = Get-ChildItem $FileFolder -Recurse | where-object { (get-acl $_.FullName).owner -ne $CurrentUser }  
79:            #If no files/folders were added to $Items, then it is a success  
80:            If ((($Item -ne "") -and ($Item -ne $null)) -and (($Items -ne "") -and ($Items -ne $null))) {  
81:                 $Output += "Failed"  
82:            } else {  
83:                 $Output += "Success"  
84:            }  
85:       } else {  
86:            [string]$FolderOwner = (get-acl $FileFolder).owner  
87:            If ($CurrentUser -ne $FolderOwner) {  
88:                 $Output += "Failed"  
89:                 $Errors = $true  
90:            } else {  
91:                 $Output += "Success"  
92:            }  
93:       }  
94:       Write-ToDisplay -Output $Output  
95:       If ($Errors -eq $true) {  
96:            #Error 5 is an arbitrary number I chose to flag if this fails  
97:            Exit 5  
98:       }  
99:  }  
100:    
101:  function Write-ToDisplay {  
102:  <#  
103:       .SYNOPSIS  
104:            Output Success/Failure to Display  
105:         
106:       .DESCRIPTION  
107:            Write the output to the Display color coded yellow for success and red for failure  
108:         
109:       .PARAMETER Output  
110:            Data to display to the screen  
111:         
112:       .EXAMPLE  
113:                      PS C:\> Write-ToDisplay -Output 'Value1'  
114:         
115:       .NOTES  
116:            Additional information about the function.  
117:  #>  
118:         
119:       [CmdletBinding()]  
120:       param  
121:       (  
122:            [ValidateNotNullOrEmpty()]$Output  
123:       )  
124:         
125:       $OutputSplit = (($Output.Replace(".", " ")).Replace("   ", ".")).Split(".")  
126:       Write-Host $OutputSplit[0]"....." -NoNewline  
127:       If ($OutputSplit[1] -like "*Success*") {  
128:            Write-Host $OutputSplit[1] -ForegroundColor Yellow  
129:       } elseif ($OutputSplit[1] -like "*Fail*") {  
130:            Write-Host $OutputSplit[1] -ForegroundColor Red  
131:       }  
132:  }  
133:    
134:  Grant-FolderOwnership -FileFolder $FilesFolders  
135:    

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: