Reputation: 1904
I will to use WMI to query the servers for the administrators group memberships. My question is : I want to fetch account status via WMI query too. Btw , no required status for Group such as domain admins and so on.
My desired output:
"UserName","Fullname","Machinename","DomainName","Account Status"
"localuser","MACHINE\localuser","MACHINE","MACHINE","OK"
"Domain Admins","CONTOSO\Domain Admins","MACHINE","CONTOSO"
"domain_user_01","CONTOSO\domain_user_01","MACHINE","CONTOSO","Degraded"
Script :
function get-localadministrators {
param ([string]$computername=$env:computername)
$computername = $computername.toupper()
$ADMINS = get-wmiobject -computername $computername -query "select * from win32_groupuser where GroupComponent=""Win32_Group.Domain='$computername',Name='administrators'""" | % {$_.partcomponent}
foreach ($ADMIN in $ADMINS) {
$admin = $admin.replace("\\$computername\root\cimv2:Win32_UserAccount.Domain=","") # trims the results for a user
$admin = $admin.replace("\\$computername\root\cimv2:Win32_Group.Domain=","") # trims the results for a group
$admin = $admin.replace('",Name="',"\")
$admin = $admin.REPLACE("""","")#strips the last "
$objOutput = New-Object PSObject -Property @{
Machinename = $computername
Fullname = ($admin)
DomainName =$admin.split("\")[0]
UserName = $admin.split("\")[1]
}#end object
$objreport+=@($objoutput)
}#end for
return $objreport
}#end function
Upvotes: 0
Views: 1631
Reputation: 13567
You said
How can I optimize this script, there are 200 machines, it will take a century...
...So , how can I write via the directoryservices .NET class instead of WMI ? is it possible? Do you have any information on this? – Arbelac 6 hours ago
If you really need to run one command on a boat load of machines and you have remote access rights, you can look at ForEach-Parallel
or Invoke-Parallel
, two different options for running one command on multiple machines.
However you will need to deal with remote state, like worrying about what to do if a remote machine is offline, or you what if a machine isn't reachable when you try to contact it.
You have an environment already, well, let's think of other ways to achieve the same goal.
You could instead use Group Policy to run this command locally on each machine as a log on script. The machines can then write their output to a Json
or Csv
file in some central share, maybe ADFS if you have it.
You could also use any remote agent you have installed on the machines as well, like System Center Configuration Manager, or Shalvik, or any of the other apps you might have.
So to boil down my answer, you can do this with PowerShell Remoting but it is starting to sound like that will cause you problems, and you might want to think about distributing the job out using some other system.
Source: was a sysadmin and automation engineer for years. I would probably not use PowerShell remoting like this.
Upvotes: 0
Reputation: 16106
Continuing from my comment.
You can use WMI or ADSI to do this, but PowerShell v5 and higher already have cmdlets for this use case.
All of the below are using the raw property names from the classes. Of course, if you want a different name, you can use a hash table, PSCustomObject, or calculated property to do that.
Get-Command -Module '*local*' |
Format-Table -AutoSize
# Results
<#
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Add-LocalGroupMember 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Disable-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Enable-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Get-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Get-LocalGroupMember 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Get-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet New-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet New-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Remove-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Remove-LocalGroupMember 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Remove-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Rename-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Rename-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Set-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts
Cmdlet Set-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts
#>
If you are on a PSVersion that does not have this by default, you can get one here:
Find-Module -Name '*local*' |
Format-Table -AutoSize
# Results
<#
Version Name Repository Description
------- ---- ---------- -----------
...
1.6 localaccount PSGallery A Simple module to allow the management of local users and groups on a computer
1.0.0.0 Microsoft.PowerShell.LocalAccounts PSGallery Provides cmdlets to work with local users and local groups
3.0 LocalUserManagement PSGallery a module that performs various local user management functions
1.3 LocalMachine PSGallery Simple management functions for accounts and settings on a local machine.
0.1.1 LocalAccountManagement PSGallery Manage local and remote user accounts and profiles
...
1.0.1 cLocalGroup PSGallery The cLocalGroup module contains the cLocalGroup DSC resource that provides a mecha...
2.1.0 ECS.LocalGPO PSGallery This Windows PowerShell module contains functions used for working with Windows lo...
...
#>
If you are stuck with WMI, then...
Discover what you have to work with and any associated class relationships
# Group Detail
Clear-Host
((Get-WmiObject -Class Win32_Group) -match 'Administrators')[0] |
Select-Object -Property '*'
<#
PSComputerName : 570A5E12-BA93-4
Status : OK
Name : Administrators
__GENUS : 2
__CLASS : Win32_Group
__SUPERCLASS : Win32_Account
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : Win32_Group.Domain="570A5E12-BA93-4",Name="Administrators"
__PROPERTY_COUNT : 9
__DERIVATION : {Win32_Account, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER : 570A5E12-BA93-4
__NAMESPACE : root\cimv2
__PATH : \\570A5E12-BA93-4\root\cimv2:Win32_Group.Domain="570A5E12-BA93-4",Name="Administrators"
Caption : 570A5E12-BA93-4\Administrators
Description : Administrators have complete and unrestricted access to the computer/domain
Domain : 570A5E12-BA93-4
InstallDate :
LocalAccount : True
SID : S-1-5-32-544
SIDType : 4
...
#>
Get-WmiObject -Class Win32_Group |
Select-Object -Property Name, SIDType
# Results
<#
Name SIDType
---- -------
...
Administrators 4
...
Guests 4
...
Users 4
#>
# User Detail
Clear-Host
(Get-WmiObject -Class Win32_Account)[0] |
Select-Object -Property '*'
<#
PSComputerName : 570A5E12-BA93-4
Status : Degraded
Caption : 570A5E12-BA93-4\Administrator
PasswordExpires : False
__GENUS : 2
__CLASS : Win32_UserAccount
__SUPERCLASS : Win32_Account
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : Win32_UserAccount.Domain="570A5E12-BA93-4",Name="Administrator"
__PROPERTY_COUNT : 16
__DERIVATION : {Win32_Account, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER : 570A5E12-BA93-4
__NAMESPACE : root\cimv2
__PATH : \\570A5E12-BA93-4\root\cimv2:Win32_UserAccount.Domain="570A5E12-BA93-4",Name="Administrator"
AccountType : 512
Description : Built-in account for administering the computer/domain
Disabled : True
Domain : 570A5E12-BA93-4
FullName :
InstallDate :
LocalAccount : True
Lockout : False
Name : Administrator
PasswordChangeable : True
PasswordRequired : True
SID : S-1-5-21-2047949552-857980807-821054962-500
SIDType : 1
...
#>
Get-WmiObject -Class Win32_Account |
Select-Object -Property Name, SIDType
# Results
<#
Name SIDType
---- -------
Administrator 1
DefaultAccount 1
Guest 1
WDAGUtilityAccount 1
Everyone 5
...
BUILTIN 3
...
Administrators 4
...
Guests 4
...
Users 4
#>
The below will get you all users and the groups to which that belong by select the user first, which is what you are showing in your post, but the is really short-circuit logic since you are only looking for uses in your query in Administrators. So, this...
Select only what you need. Note the focus regarding the SIDType code as a filter
Clear-Host
Get-WmiObject -Class Win32_Account |
Where-Object -Property SIDType -eq 1 |
Select-Object -Property PSComputerName, Name, Status,
@{
Name = 'Groups'
Expression = {($PSItem).GetRelated('Win32_Group').Name}
} |
Format-Table -AutoSize
# Results
<#
PSComputerName Name Status Groups
-------------- ---- ------ ------
570A5E12-BA93-4 Administrator Degraded Administrators
570A5E12-BA93-4 DefaultAccount Degraded System Managed Accounts Group
570A5E12-BA93-4 Guest Degraded Guests
570A5E12-BA93-4 WDAGUtilityAccount OK {Administrators, Remote Desktop Users}
#>
Of course, you can use RegEx to get rid of the braces if that's your thing. Lastly, remove the Where-Object line, the SIDType filter and you get it all.
Clear-Host
Get-WmiObject -Class Win32_Account |
Select-Object -Property PSComputerName, Name, Status,
@{
Name = 'Groups'
Expression = {($PSItem).GetRelated('Win32_Group').Name}
} |
Format-Table -AutoSize
# Results
<#
PSComputerName Name Status Groups
-------------- ---- ------ ------
570A5E12-BA93-4 Administrator Degraded Administrators
570A5E12-BA93-4 DefaultAccount Degraded System Managed Accounts Group
570A5E12-BA93-4 Guest Degraded Guests
570A5E12-BA93-4 WDAGUtilityAccount OK {Administrators, Remote Desktop Users}
570A5E12-BA93-4 Everyone OK
...
570A5E12-BA93-4 NETWORK OK
570A5E12-BA93-4 BATCH OK
570A5E12-BA93-4 INTERACTIVE OK Users
...
570A5E12-BA93-4 SELF OK
570A5E12-BA93-4 Authenticated Users OK Users
570A5E12-BA93-4 RESTRICTED OK
...
570A5E12-BA93-4 IUSR OK IIS_IUSRS
...
#>
Reverse the request --- Select only what you need, SIDType not needed.
Clear-Host
Get-WmiObject -Class Win32_Group |
Select-Object -Property PSComputerName, Name, Status,
@{
Name = 'GroupMembers'
Expression = {
(
Get-WmiObject -Class win32_group |
Where Name -eq $PSItem.Name).GetRelated('Win32_UserAccount'
).Name
}
} |
Format-Table -AutoSize
# Results
<#
PSComputerName Name Status GroupMembers
-------------- ---- ------ ------------
570A5E12-BA93-4 Access Control Assistance Operators OK
570A5E12-BA93-4 Administrators OK {Administrator, WDAGUtilityAccount}
570A5E12-BA93-4 Backup Operators OK
570A5E12-BA93-4 Cryptographic Operators OK
570A5E12-BA93-4 Device Owners OK
570A5E12-BA93-4 Distributed COM Users OK
570A5E12-BA93-4 Event Log Readers OK
570A5E12-BA93-4 Guests OK Guest
570A5E12-BA93-4 Hyper-V Administrators OK
570A5E12-BA93-4 IIS_IUSRS OK
570A5E12-BA93-4 Network Configuration Operators OK
570A5E12-BA93-4 Performance Log Users OK
570A5E12-BA93-4 Performance Monitor Users OK
570A5E12-BA93-4 Power Users OK
570A5E12-BA93-4 Remote Desktop Users OK WDAGUtilityAccount
570A5E12-BA93-4 Remote Management Users OK
570A5E12-BA93-4 Replicator OK
570A5E12-BA93-4 System Managed Accounts Group OK DefaultAccount
570A5E12-BA93-4 Users OK
#>
If PSRemoting (either domain or workgroup mode) is properly setup, hitting remote systems is straightforward.
# Target a remote computer
Clear-Host
Import-Csv -Path 'D:\Temp\ComputerList.csv' |
ForEach-Object {
Get-WmiObject -Class Win32_Account -ComputerName $PSitem.Name -Credential (Get-Credential -Credential WDAGUtilityAccount) |
Where-Object -Property SIDType -eq 1 |
Select-Object -Property PSComputerName, Name, Status,
@{
Name = 'Groups'
Expression = {($PSItem).GetRelated('Win32_Group').Name}
}
} |
Format-Table -AutoSize
# Results
<#
PSComputerName Name Status Groups
-------------- ---- ------ ------
570A5E12-BA93-4 Administrator Degraded Administrators
570A5E12-BA93-4 DefaultAccount Degraded System Managed Accounts Group
570A5E12-BA93-4 Guest Degraded Guests
570A5E12-BA93-4 TestUser OK Users
570A5E12-BA93-4 WDAGUtilityAccount OK {Administrators, Remote Desktop Users}
#>
As for your particulars
<#
"UserName","Fullname","Machinename","DomainName","Account Status"
"localuser","MACHINE\localuser","MACHINE","MACHINE","OK"
"Domain Admins","CONTOSO\Domain Admins","MACHINE","CONTOSO"
"domain_user_01","CONTOSO\domain_user_01","MACHINE","CONTOSO","Degraded"
#>
# Only the administrators group
Clear-Host
Get-WmiObject -Class Win32_Account |
Where-Object -Property SIDType -eq 1 |
Select-Object -Property Name, Caption, PSComputerName, Domain, Status,
@{
Name = 'Groups'
Expression = {($PSItem).GetRelated('Win32_Group').Name}
} |
Where-Object -Property Groups -EQ 'Administrators'|
Format-Table -AutoSize
# Results
<#
Name Caption PSComputerName Domain Status Groups
---- ------- -------------- ------ ------ ------
Administrator 570A5E12-BA93-4\Administrator 570A5E12-BA93-4 570A5E12-BA93-4 Degraded Administrators
WDAGUtilityAccount 570A5E12-BA93-4\WDAGUtilityAccount 570A5E12-BA93-4 570A5E12-BA93-4 OK {Administrators, Remote Desktop Users}
#>
Clear-Host
Get-WmiObject -Class Win32_Group |
Select-Object -Property Name, Caption, PSComputerName, Domain, Status,
@{
Name = 'GroupMembers'
Expression = {
(
Get-WmiObject -Class win32_group |
Where Name -eq $PSItem.Name).GetRelated('Win32_UserAccount'
).Name
}
} |
Format-Table -AutoSize
# Results
<#
Name Caption PSComputerName Domain Status GroupMembers
---- ------- -------------- ------ ------ ------------
Access Control Assistance Operators 570A5E12-BA93-4\Access Control Assistance Operators 570A5E12-BA93-4 570A5E12-BA93-4 OK
Administrators 570A5E12-BA93-4\Administrators 570A5E12-BA93-4 570A5E12-BA93-4 OK {Administrator, WDAGUtilityAccount}
...
#>
# Only the administrators group
Clear-Host
Get-WmiObject -Class Win32_Group |
Where-Object -Property Name -eq 'Administrators' |
Select-Object -Property Name, Caption, PSComputerName, Domain, Status,
@{
Name = 'GroupMembers'
Expression = {
(
Get-WmiObject -Class win32_group |
Where Name -eq $PSItem.Name).GetRelated('Win32_UserAccount'
).Name
}
} |
Format-Table -AutoSize
# Results
<#
Name Caption PSComputerName Domain Status GroupMembers
---- ------- -------------- ------ ------ ------------
Administrators 570A5E12-BA93-4\Administrators 570A5E12-BA93-4 570A5E12-BA93-4 OK {Administrator, WDAGUtilityAccount}
#>
Update as per your comment.
As for this...
can I write via the directoryservices .NET class instead of WMI
Sure you can, as detailed here...
https://devblogs.microsoft.com/scripting/the-admins-first-steps-local-group-membership
... but using this does not give you back the output you list.
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $env:COMPUTERNAME
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, ‘Administrators’)
$group.Members |
Select-Object -Property '*' -First 1
# Results
<#
GivenName :
MiddleName :
Surname :
EmailAddress :
VoiceTelephoneNumber :
EmployeeId :
AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters
Enabled : False
AccountLockoutTime :
LastLogon :
PermittedWorkstations : {}
PermittedLogonTimes : {255, 255, 255, 255...}
AccountExpirationDate :
SmartcardLogonRequired : False
DelegationPermitted : True
BadLogonCount : 0
HomeDirectory :
HomeDrive :
ScriptPath :
LastPasswordSet : 3/28/2021 10:20:29 AM
LastBadPasswordAttempt :
PasswordNotRequired : False
PasswordNeverExpires : True
UserCannotChangePassword : False
AllowReversiblePasswordEncryption : False
Certificates : {}
Context : System.DirectoryServices.AccountManagement.PrincipalContext
ContextType : Machine
Description : Built-in account for administering the computer/domain
DisplayName :
SamAccountName : Administrator
UserPrincipalName :
Sid : S-1-5-21-2047949552-857980807-821054962-500
Guid :
DistinguishedName :
StructuralObjectClass :
Name : Administrator
#>
Upvotes: 2