Installation Guide

Inventory Local Administrators Group

In order to populate the data required to see members of the local administrators group you must first add the data to WMI using the following script. The recommended way to do this is using a Configuration Baseline. Once the data is in WMI on a test computer you can then add it as custom hardware inventory. Skipping this step will not generate any errors however, the "Antivirus & Antispyware" page will not populate. The Antivirus & Antispyware page reports on both third-party and Microsoft Defender installation and activation status.

For more information on extending Configuration Manager hardware inventory see the section Add a new inventory class on the How to extend hardware inventory in Configuration Manager documentation page.

Originally, the information on how to leverage the script, as well as the script itself was posted on Article: Audit Local Administrators Group with SCCM (itninja.com) The only change that we have made to the original script found on GitHub was to change the name of the WMI class, this is done by changing $InventoryWMIClass = 'LocalSecurityGroupInventory' to $InventoryWMIClass = 'FATSTACKS_LOCAL_ADMINS'.

After making the minor change above to the Inventory-LocalSecurityGroup.ps1 script create a Configuration Item in Configuration Manager as follows:


  1. In the Configuration Manager console, go to the Assets and Compliance workspace, expand Compliance Settings, and select the Configuration Items node.

  2. On the Home tab of the ribbon, in the Create group, select Create Configuration Item.

  3. On the General page of the Create Configuration Item Wizard, specify a name, and optional description for the configuration item and then click Next.

  4. Under Specify the type of configuration item that you want to create, select Windows Desktops and Servers (custom) and then click Next.

  5. On the Supported Platforms tab of the Create Configuration Item Wizard, specify the applicable platforms for which the item will be applied and then click Next.

  6. On the Settings tab of the Create Configuration Items Wizard, click New, give the setting a name such as “Add Local Admin Members to WMI for Reporting, change the Setting type to Script, and the Data type to Integer. In the Discovery script section paste the contents of the Inventory-LocalSecurityGroup.ps1 script, click Ok.

  7. On the Settings tab of the Create Configuration Items Wizard, click Next.

  8. On the Compliance Rules tab of the Create Configuration Items Wizard, click New and name the rule "WMI Class Detected", click Browse and select the setting create in Step 6, click Select, set the rule type as Value, and the value returned by the specified script equals "0".

  9. On the Compliance Rules tab of the Create Configuration Items Wizard, click Next.

  10. On the Summary tab of the Create Configuration Items Wizard, click Next.

  11. On the Completion tab of the Create Configuration Items Wizard, click Close.

<#

.SYNOPSIS

Inventory-LocalSecurityGroup.ps1

Script to assist with auditing members of a Local Security Group through SCCM.

.DESCRIPTION

Intended Use

This script intended to be used in conjunction with SCCM Configuration Baselines. Providing

SCCM access to this information through Hardware Inventory, with the end outcome of the data

being presented in SCCM reports.


Please see the article for detailed implementation into SCCM.


Warning: Verbose must be SilentlyContinue when in production, otherwise CCM Agent will state

it is non-compliant. CCM Agent expects a return of a Boolean value for success.



Error Codes

1: Compliance script failed due to orphaned SIDs.

2: Compliance script failed due to the WMI class being empty.



About

The script is a direct copy from https://gist.github.com/rileyz/40ec2e8ca9c15c85ce2de70bd1f5fdea with one minor edit to make the view name in SCCM follow the Fatstacks naming standard.


Known Defects/Bugs

* If the cmdlet Get-LocalGroupMember is unable to run, then the script will not return the SID

for any of the accounts in that particular Local Security Group by design.


* If the cmdlet Get-LocalGroupMember is unable to run, then the script will be unable to discover

any orphaned objects in that particular Local Security Group.



Code Snippet Credits

* https://mickitblog.blogspot.com/2016/11/sccm-local-administrators-reporting.html

* https://github.com/MicksITBlogs/PowerShell/blob/master/LocalAdmins.ps1

* https://stackoverflow.com/questions/31949541/print-local-group-members-in-powershell-5-0/35064645

Version History

1.00 25/03/2020

Initial release.

1.1 4/9/2021

Change the WMI class name to fit FatStacks standards

Copyright & Intellectual Property

Feel to copy, modify and redistribute, but please pay credit where it is due.

Feedback is welcome, please contact me on LinkedIn.

.LINK

Author:.......http://www.linkedin.com/in/rileylim

Source Code:..https://gist.github.com/rileyz/40ec2e8ca9c15c85ce2de70bd1f5fdea

Article:......https://www.itninja.com/blog/view/audit-local-administrator-group-with-sccm


#>




# Function List ###################################################################################

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 enough to collect the WMI data.

.EXAMPLE

PS C:\> Invoke-SCCMHardwareInventory

.NOTES

Additional information about the function.

#>

[CmdletBinding()]

param ()

if (((Get-WmiObject -Class "SMS_Client" -List -Namespace 'root\ccm' -ErrorAction SilentlyContinue) -ne $null)) {

Write-Verbose "Found CCM Agent, performing Hardware Inventory."

$ComputerName = $env:COMPUTERNAME

$SMSCli = [wmiclass] "\\$ComputerName\root\ccm:SMS_Client"

$SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}") | Out-Null

}

else {

Write-Warning "CCM Agent not found, will not perform Hardware Inventory!"

}

}


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-Verbose $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("PrimaryKey", [System.Management.CimType]::String, $false)

$newClass.Properties["PrimaryKey"].Qualifiers.Add("key", $true)

$newClass.Properties["PrimaryKey"].Qualifiers.Add("read", $true)

$newClass.Properties.Add("SID", [System.Management.CimType]::String, $false)

$newClass.Properties["SID"].Qualifiers.Add("key", $true)

$newClass.Properties["SID"].Qualifiers.Add("read", $true)

$newClass.Properties.Add("LocalSecurityGroup", [System.Management.CimType]::String, $false)

$newClass.Properties["LocalSecurityGroup"].Qualifiers.Add("key", $true)

$newClass.Properties["LocalSecurityGroup"].Qualifiers.Add("read", $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-Verbose $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 @{PrimaryKey = $LocalAdministrator.PrimaryKey;

SID = $LocalAdministrator.SID;

LocalSecurityGroup = $LocalAdministrator.LocalSecurityGroup;

Domain = $LocalAdministrator.Domain;

User = $LocalAdministrator.User}


if ($Return -like "*" + $LocalAdministrator.User + "*") {

$Output += "Success"

} else {

$Output += "Failed"

}

Write-Verbose $Output

}

}

#<<< End Of Function List >>>




# Setting up housekeeping #########################################################################

$VerbosePreference = 'SilentlyContinue' #SilentlyContinue|Continue

$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition


$LocalSecurityGroups = @('Administrators') #'Administrators'|'Users'|'Administrators','Users'

$OutputFileLocation = '' #''|C:\Windows\Logs

$SCCMReporting = $true

#<<< End of Setting up housekeeping >>>


 

 

# Start of script work ############################################################################

$GetLocalGroupAlternative = @()

$i = $null

$InventoryWMIClass = 'FATSTACKS_LOCAL_ADMINS'

$LocalInventory = @()


#Use PowerShell 2.0 member enumeration for Windows Server 2008R2 compatibility 'Get-CimInstance' instead of 'Get-LocalGroup'.

$GetLocalGroupAlternative = Get-WmiObject -Class Win32_Group -Filter "Domain = '$env:COMPUTERNAME'"


foreach ($LocalSecuirtyGroup in $LocalSecurityGroups) {

#Use PowerShell 2.0 member enumeration for Windows Server 2008R2 compatibility '@($GetLocalGroupAlternative | Select-Object -Expand Name)' instead of '$GetLocalGroupAlternative.Name'.

if (!$(@($GetLocalGroupAlternative | Select-Object -Expand Name) -contains $LocalSecuirtyGroup)) {

Write-Warning "Skipping Local Security Group '$LocalSecuirtyGroup' as it does not exist!"

continue

}

Write-Verbose 'Clearing error variable.'

$Error.Clear()


Write-Verbose "Get members of Local Security Group '$LocalSecuirtyGroup'."

Try {

$Members = Get-LocalGroupMember $LocalSecuirtyGroup -ErrorAction SilentlyContinue

}

Catch{

Write-Verbose 'Get-LocalGroupMember had an issue, most likely with orphaned SIDs or cmdlet Get-LocalGroupMember failed.'

}


if ($Error.Count -ne 0) {

Write-Verbose 'Using legacy method for backwards compatibility.'


$Group = [ADSI]("WinNT://localhost/$LocalSecuirtyGroup,group")


foreach ($Member in $($Group.Members())) {

$AdsPath = $Member.GetType.Invoke().InvokeMember("Adspath", 'GetProperty', $null, $Member, $null)

# Domain members will have an ADSPath like WinNT://DomainName/UserName.

# Local accounts will have a value like WinNT://DomainName/ComputerName/UserName.

$a = $AdsPath.split('/',[StringSplitOptions]::RemoveEmptyEntries)

$name = $a[-1]

$domain = $a[-2]

$class = $Member.GetType.Invoke().InvokeMember("Class", 'GetProperty', $null, $Member, $null)


$LocalInventory += $i++ | select @{Label='PrimaryKey'; Expression={$i}},

@{Label='SID'; Expression={'Unknown'}},

@{Label='LocalSecurityGroup'; Expression={$LocalSecuirtyGroup}},

@{Label='Domain'; Expression={Switch -Exact ($domain) {

"$env:COMPUTERNAME" {'BUILTIN'}

'WinNT:' {'Unknown'}

default {$domain}}}},

@{Label="User"; Expression={$name}}

}

}

else {

Write-Verbose 'Get-LocalGroupMember ran OK, and didnt run into orphaned SID problems.'

foreach ($Member in $Members) {

#Create new object

$Admin = New-Object -TypeName System.Management.Automation.PSObject

$MemberAccount = $Member.Name.Split("\")


if ($MemberAccount[0] -contains $env:COMPUTERNAME) {

$LocalInventory += $i++ | select @{Label='PrimaryKey'; Expression={$i}},

@{Label='SID'; Expression={$Member.SID}},

@{Label='LocalSecurityGroup'; Expression={$LocalSecuirtyGroup}},

@{Label='Domain'; Expression={'BUILTIN'}},

@{Label='User'; Expression={$MemberAccount[1]}}

}

else {

$LocalInventory += $i++ | select @{Label='PrimaryKey'; Expression={$i}},

@{Label='SID'; Expression={$Member.SID}},

@{Label='LocalSecurityGroup'; Expression={$LocalSecuirtyGroup}},

@{Label='Domain'; Expression={$MemberAccount[0]}},

@{Label='User'; Expression={$MemberAccount[1]}}

}

}

}

}


Write-Verbose 'Report output to WMI which will report up to SCCM.'

if ($SCCMReporting) {

New-WMIClass -Class "$InventoryWMIClass"

New-WMIInstance -Class "$InventoryWMIClass" -LocalAdministrators $LocalInventory

Write-Verbose 'Report WMI entry to SCCM.'

Invoke-SCCMHardwareInventory

}


if ($OutputFileLocation -ne '') {

Write-Verbose "Writing to file location '$OutputFileLocation'"

$FileName = "Inventory-LocalAdministratorsGroup"

if ($OutputFileLocation[$OutputFileLocation.Length - 1] -ne "\") {

$File = $OutputFileLocation + "\" + $FileName + ".log"

} else {

$File = $OutputFileLocation + $FileName + ".log"

}

Write-Verbose 'Delete old log file if it exists.'

$Output = "Deleting $FileName.log....."

if ((Test-Path $File) -eq $true) {

Remove-Item -Path $File -Force

}

if ((Test-Path $File) -eq $false) {

$Output += "Success"

} else {

$Output += "Failed"

}

Write-Verbose $Output

$Output = "Writing local admins to $FileName.log....."

$LocalInventory | Format-Table | Out-File $File

if ((Test-Path $File) -eq $true) {

$Output += "Success"

} else {

$Output += "Failed"

}

Write-Verbose $Output

}


Write-Verbose "Display members Local Security Group '$LocalSecurityGroups'."

Write-Verbose "$($LocalInventory | Format-Table | Out-String)"


if (((Get-WmiObject -Class "$InventoryWMIClass" -ComputerName $env:COMPUTERNAME -Namespace 'ROOT\cimv2').Count -gt 0)) {

#Use PowerShell 2.0 member enumeration for Windows Server 2008R2 compatibility.

if ((@($LocalInventory| Select-Object -Expand User) -match 'S-1-5-21').Count -ne 0) {

Write-Verbose 'Return 1 to SCCM, to notify compliance script failed due to orphaned SIDs.'

return 1

}

else {

Write-Verbose 'Return true to SCCM, to notify compliance script ran OK.'

return 0

}

}

else {

Write-Verbose 'Return 2 to SCCM, to notify compliance script failed due to the WMI class being empty.'

return 2

}

#<<< End of script work >>>


After creating the Configuration Item create and deploy and Configuration Baseline in Configuration Manager as follows:


1. In the Configuration Manager console, click Assets and Compliance > Compliance Settings > Configuration Baselines.

2. On the Home tab, in the Create group, click Create Configuration Baseline.

3. In the Create Configuration Baseline dialog box, enter a unique name, and a description for the configuration baseline, click Ok.

4. Below the Configuration data pane, click Add to add a new configuration item or configuration baseline to the list, choose the Configuration Item created earlier, click Ok.

5. In co-managed environments where the Compliance Policies workload has been moved to Intune you must select if you want to Always apply this baseline for co-managed clients

6. On the Add Configuration Items pane of the Create Configuration Baseline Wizard click Ok.

7. Right click on the newly created Configuration Baseline and select Deploy.

8. Click Browse to select the collection where you want to deploy the configuration baseline.

9. Specify the compliance evaluation schedule for the Configuration Baseline.

10. Click Ok to complete the deployment of the Configuration Baseline

Add the new WMI class to Configuration Manager by extending hardware inventory to include the class:

You can only add inventory classes from the hierarchy's top-level server by modifying the default client settings. This option isn't available when you create custom device settings.

1. In the Configuration Manager console, choose Administration > Client Settings > Default Client Settings.

2. On the Home tab, in the Properties group, choose Properties.

3. In the Default Client Settings dialog box, choose Hardware Inventory.

4. In the Device Settings list, choose Set Classes.

5. In the Hardware Inventory Classes dialog box, choose Add.

6. In the Add Hardware Inventory Class dialog box, select Connect.

7. In the Connect to Windows Management Instrumentation (WMI) dialog box, specify the name of the computer from which you willretrieve the WMI classes and the WMI namespace to use for retrieving the classes, select Recursive.

8. Choose Connect.

9. In the Add Hardware Inventory Class dialog box, in the Inventory classes list, select the FATSTACKS_LOCAL_ADMINS WMI. Click Edit, and in the Class qualifiers dialog box ensure that all available properties are checked. Click Ok to close the Class qualifiers dialog box.

10. Click Ok to return to the Hardware Inventory Classes dialog box. We recommend that you unselect the FATSTACKS_LOCAL_ADMINS class in the Default Client Settings and instead add it into a custom client setting. Click Ok to return to the Default Client Settings.

11. Click Ok to close the Default Client Settings.

12. If you chose to leave the FATSTACKS_LOCAL_ADMINS class selected in the Default Client Settings you have completed the required steps for collecting the members of the Local Administrators group. Otherwise, add the FATSTACKS_LOCAL_ADMINS class to Hardware Inventory in a custom client setting.