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  
   

3 comments:

  1. Great post and solution, I'm finding that only two entries per machines are making their way into the LOCAL_ADMINISTRATORS_DATA table?

    ReplyDelete
  2. Hi, really great solution, but i have the same problem as "Unknown" above ;)
    There are only two entries :(

    ReplyDelete
  3. Me too! The script show the right list but the query sccm list only two entries: Administrators and Domain Admins

    ReplyDelete