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:
Enter the computer name of the system you ran the script on
Check Recursive
Check Credentials required
Enter the domain\username for user name
Enter the associated password
Click Connect
Once the list of classes appears, click on Class Name to sort the classes
Scroll down to find Local_Administrators and check the box to the left
Click OK
Click OK
Click OK
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 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
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:
Go into SCCM--->Administration Tab--->Client Settings---> Default Client Settings--->Hardware Inventory--->Set Classes.
Click Add--->Connect.
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.
Click Connect
Click on the Class Name tab to sort by class name
Scroll down to find MappedDrives and check the box
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:
Right-click on a machine in the Assets and Compliance--->Devices
Click Start--->Resource Explorer
Click the plus beside Hardware
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:
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.
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.
<#
.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
}
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.
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:
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.
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: