Only enabled Administrator accounts - powershell

I use The below script to get remote administrator account names, I get all the administrator enabled / disabled. I wanted to just get enabled administrator accounts / get the status of the account whether it is enabled / disabeld. Have tried but failed. Can anyone please help.
$Computers = Get-Content "D:\doc\Work\sCRIPTS\servers.txt"
foreach ($Computer in $Computers) {
$strcomputer = [ADSI]("WinNT://" + $Computer + ",computer")
$Group = $strcomputer.psbase.children.find("Administrators")
$members= $Group.psbase.invoke("Members") | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$Computer | Add-Content D:\doc\Work\sCRIPTS\export.xls
ForEach($user in $members){
$user | Add-Content D:\doc\Work\sCRIPTS\export.xls
}
Write-Host ""
}

I think you need to change the end of your script by retrieving each user object member of the Administrators group and verify that it is not Disabled in the userFlags property of the object.
$Computers = Get-Content "D:\doc\Work\sCRIPTS\servers.txt"
foreach ($Computer in $Computers) {
$strcomputer = [ADSI]("WinNT://" + $Computer + ",computer")
$Group = $strcomputer.psbase.children.find("Administrators")
$members= $Group.psbase.invoke("Members") | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$Computer | Add-Content D:\doc\Work\sCRIPTS\export.xls
ForEach($user in $members)
{
$userObj = [ADSI]("WinNT://" +$computer+"/"+ $user)
if ($userObj.UserFlags -ne $null)
{
$flags = $userObj.UserFlags[0]
if ($flags -band 512) # 512 = enabled, 2 = disabled
{
$user | Add-Content D:\doc\Work\sCRIPTS\export.xls
}
}
}
}

Related

Unable to get the members list from local administrator group through powershell

I am unable to get the list of members from local administrator group after running the following command. its showing the error " missing closing '}' every time. Please help me on it or if have another scripts then please share with me.
function get-localusers {
param (
[Parameter(Mandatory=$true,valuefrompipeline=$true)]
[string]$strComputer
)
begin {}
Process {
$adminlist =""
$powerlist =""
$computer = [ADSI]("WinNT://" + $strComputer + ",computer")
$AdminGroup = $computer.psbase.children.find("Administrators")
$powerGroup = $computer.psbase.children.find("Power Users")
$powerGroup = $computer.psbase.children.find("Power Users")
$Adminmembers= $AdminGroup.psbase.invoke("Members") | % {
$.GetType().InvokeMember("Name", 'GetProperty', $null, $, $null)
}
$Powermembers= $PowerGroup.psbase.invoke("Members") | % {
$.GetType().InvokeMember("Name", 'GetProperty', $null, $, $null)
}
foreach ($admin in $Adminmembers) {
$adminlist = $adminlist + $admin + ","
}
foreach ($poweruser in $Powermembers) {
$powerlist = $powerlist + $poweruser + ","
}
$Computer = New-Object psobject $computer | Add-Member noteproperty ComputerName $strComputer $computer | Add-Member noteproperty Administrators $adminlist $computer | Add-Member noteproperty PowerUsers $powerlist
Write-Output $computer
}
end {}
}
Get-Content C:\temp\server_list.txt | get-localusers | Export-Csv C:\temp\localusers.csv
If you what to reference to the object in a Foreach-Object (%) block you have to use $_
Change the Foreach-Object part in your code to this:
$Adminmembers= $AdminGroup.psbase.invoke("Members") | % {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
$Powermembers= $PowerGroup.psbase.invoke("Members") | % {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}

need to export Powershell results to file

I have the following powershell script, I am unsure how to get it to export all the results to a file. Needs to have $Computer,$Group.Name, $Name preferably in CSV format
here is my query
$Computer = "ChynaSyndrome"
$Computer = [ADSI]"WinNT://$Computer"
$Groups = $Computer.psbase.Children | Where {$_.psbase.schemaClassName -eq "group"}
ForEach ($Group In $Groups)
{
"Group: " + $Group.Name
$Members = #($Group.psbase.Invoke("Members"))
ForEach ($Member In $Members)
{
$Class = $Member.GetType().InvokeMember("Class", 'GetProperty', $Null, $Member, $Null)
$Name = $Member.GetType().InvokeMember("Name", 'GetProperty', $Null, $Member, $Null)
"-- Member: $Name ($Class)"
}
}
This will still give you console output, but it will build a PSObject with each member found in the group, then add those objects to the $results array. Once done, you have the option to show the resulting array in a GridView popup window and/or exporting them to CSV on your Desktop. Comment out either line to not take that action:
** Update: Per comments, I parameterized the script, allowing the calling process to provide an array of computer names (or a single one). Calling this script from a batch file would work like this:
powershell.exe -File "C:\script.ps1" -ComputerName "ChynaSyndrome","ChynaSyndrome2","ChynaSyndrome3"
Script.ps1:
Param
(
[parameter(Mandatory=$true,Position=0)]
[String[]]
$ComputerName
)
Begin {
$results = #()
}
Process {
foreach ($Computer in $ComputerName) {
$ComputerADSI = [ADSI]"WinNT://$Computer"
$Groups = $ComputerADSI.psbase.Children | Where-Object {$_.psbase.schemaClassName -eq "group"}
ForEach ($Group In $Groups) {
"Group: " + $Group.Name
$Members = #($Group.psbase.Invoke("Members"))
ForEach ($Member In $Members) {
$Class = $Member.GetType().InvokeMember("Class", 'GetProperty', $Null, $Member, $Null)
$Name = $Member.GetType().InvokeMember("Name", 'GetProperty', $Null, $Member, $Null)
"-- Member: $Name ($Class)"
$object = New-Object PSObject -Property #{
Computer = $Computer
GroupName = $Group.Name.ToString()
MemberName = $Name.ToString()
MemberClass = $Class
}
$results += $object
}
}
}
}
End {
# Export results to CSV on your Desktop
$results | Export-Csv -NoTypeInformation "$env:USERPROFILE\Desktop\GroupResults.csv" -Force
}

new-object PSObject causes null-valued expression error

My PowerShell script:
Param([string]$Computers) #Must supply a comma seperated list of servers
$Threshold = 20 #Only show CPU over this number
$NoP = 20 #Number of processes to list
$NoRS = 4 #Number of result sets
If (! $Computers) {
Write-Host "Connection to server failed - please specify a server name." -ForegroundColor Red
Break
} Else {
$ComputerList = $Computers -Split " "#,[StringSplitOptions]'RemoveEmptyEntries')
}
$Credential = $host.ui.PromptForCredential("Need credentials", "Please enter your user name and password.", "", "NetBiosUserName")
If (! $Credential) {
Write-Host "Authentication failed - please verify your username and password." -ForegroundColor Red
Break
}
$UserName = $Credential.Username
$Password = $Credential.GetNetworkCredential().Password
$CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
$Domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$UserName,$Password)
If ($Domain.Name -eq $null){
Write-Host "Authentication failed - please verify your username and password." -ForegroundColor Red
Break
}
ForEach ($ComputerName In $ComputerList) {
$LoadPercentage = $Processors.LoadPercentage
If (!$LoadPercentage) {$LoadPercentage = 0}
Write-Host "Server: $ComputerName (CPU load $LoadPercentage%)" -NoNewline
$Processors = Get-WmiObject win32_processor -ComputerName $ComputerName -Credential $Credential
$i = 1
$TopProcess = #()
$PercentComplete = 0
Do{
$PercentComplete = [Math]::Floor($i/$NoRS*100)
Write-Progress -Activity $ComputerName -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
$ProcessList = gwmi Win32_PerfFormattedData_PerfProc_Process -ComputerName $ComputerName -Credential $Credential |
Select IDProcess,Name,PercentProcessorTime |
Where {$_.Name -ne "_Total" -and $_.Name -ne "Idle"} |
Sort PercentProcessorTime -Descending |
Select -First $NoP
ForEach ($Process In $ProcessList) {
$row = New-Object PSObject -Property #{
Id = $Process.IDProcess
Name = $Process.Name
User = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).GetOwner().User
CPU = $Process.PercentProcessorTime/$Processors.NumberOfLogicalProcessors -f {P}
Description = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).Description
}
$TopProcess += $row
}
$i++
} While ($i -lt $NoRS + 1)
Write-Progress -Activity $ComputerName -Completed
$Group = $TopProcess | Where {$_.CPU -gt $Threshold} | Group 'ID' | Where Count -eq $NoRS
If (!$Group) {
Write-Host " has no processes persistently above $Threshold percent CPU usage."
} Else {
$Processes = #()
ForEach ($Groupee In $Group) {
$Ungroup = $Groupee | Select -ExpandProperty Group
$CPU = 0
ForEach ($ugr in $Ungroup) {
$CPU += $ugr.CPU
}
$row = new-object PSObject -Property #{
Id = $Ungroup.Id | Select -First 1
Name = $Ungroup.Name | Select -First 1
CPU = $CPU/$NoRS
User = $Ungroup.User | Select -First 1
Description = $Ungroup.Description | Select -First 1
}
$Processes += $row
}
$Processes | Format-Table #{Expression={$_.User};Label="User Name";width=25},#{Expression={$_.CPU};Label="CPU";width=5},#{Expression={$_.Id};Label="ID";width=8},#{Expression={$_.Description};Label="Description";width=48}
}
}
intermittantly gives the following error:
You cannot call a method on a null-valued expression. At C:\Users\Jasons1\CPUusage.ps1:41 char:4
$row = new-object PSObject -Property #{
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
which I fail to understand as it is within a loop and should either work or get skipped as there is a test for null.
Pretty sure that your issues are stemming from this line:
User = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).GetOwner().User
Specifically from .GetOwner(). Your where clause must not be finding a matching process for that item while it is in the loop. I realize there is not much time elapsed but WMI queries are not the fastest things out there.
What is happening is likely a result of a process queried earlier in $ProcessList = gwmi Win32_PerfFormattedData_PerfProc_Process and then later when you are using gwmi Win32_Process the list of processes changed. You need to account for this as well. Time has elapsed and threads do not live forever.
$queryResult = gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}
$owner = if($queryResult){$queryResult.GetOwner().User}else{"Process DNE"}
#...
User = $owner
Not very pretty but accounts for the potential of a null return from the wmi query.

hashtable filter / select

I was working tonight to re-write an existing server health check script to store its values in a hashtable, and that part is working fine. However, I want the results to go to a CSV file, and that file only to be populated with servers where I've tagged them as requiring action. Currently those are generating event ID 7011, or failing a ping test by Test-Connection.
Here's the code:
$CheckServer = #{}
$Servers = (Get-Content $Dir\Test.txt)
foreach ($Server in $Servers) {
$CheckServer.EventID7011 = Get-Eventlog -LogName System -ComputerName $Server -Newest 1 |
Where-Object {$_.EventId -eq 7011} | select Message
if ($CheckServer.EventID -ne $Null) {
$CheckServer.Server = "$Server"
$CheckServer.ActionReq = "Yes"
}
$CheckServer.Ping = Test-Connection -ComputerName $Server -Count 1 -Quiet
if (! $CheckServer.Ping) {
$CheckServer.Server = "$Server"
$CheckServer.ActionReq ="Yes"
$CheckServer.Ping = "Offline"
} else {
$CheckServer.Server = "$Server"
$CheckServer.ActionReq = "No"
$CheckServer.Ping = "Online"
}
New-Object -TypeName PSObject -Property $CheckServer |
Export-Csv "ScanResults.csv" -NoTypeInformation -Append
}
I need the correct code at the end, as it stands, the script works fine for collecting/storing the data in the hashtable array $CheckServer, but I'd like to only select those servers that require action. So, if I'm scanning 100 servers, and 2 of them are in a ping fail state, I want only those selected and sent to Export-Csv.
If you want only servers that don't respond to Test-Connection in the output anyway it would be much simpler to just use a Where-Object filter on the server list:
Get-Content "$Dir\Test.txt" |
Where-Object { -not (Test-Connection -Computer $_ -Count 1 -Quiet) } |
Select-Object #{n='Server';e={$_}}, #{n='ActionReq';e={'Yes'}},
#{n='Ping';e={'Offline'}} |
Export-Csv 'ScanResults.csv' -NoType -Append
You need to store the objects into a list before you can filter and export them. See the lines with comments in your code:
$CheckServer = #{}
$serverObjects = #() # create a list of server objects
$Servers = (get-content $Dir\Test.txt)
ForEach ($Server in $Servers) {
$CheckServer.EventID7011 = get-eventlog -LogName System -ComputerName
$Server -newest 1 | where-object {$_.eventID -eq 7011} |select message
If ($CheckServer.EventID -ne $Null) {
$CheckServer.Server="$Server"
$CheckServer.ActionReq = "Yes"}
$CheckServer.Ping = Test-Connection -ComputerName $Server -count 1 -quiet
if (! $CheckServer.Ping) {
$CheckServer.Server="$Server"
$CheckServer.ActionReq ="Yes"
$CheckServer.Ping= "Offline"}
Else {
$CheckServer.Server="$Server"
$CheckServer.ActionReq ="No"
$CheckServer.Ping= "Online"}
# Add the server object to the list
$serverObjects += New-Object -TypeName PSObject -Property $CheckServer
}
}
# now filter it:
$serverObjects | where ActionReq -eq "Yes" | Export-Csv -Path "...."

List all local administrator accounts excluding domain admin and local admin

function get-localgroupmember {
[CmdletBinding()]
param(
[parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$computername = $env:COMPUTERNAME
)
BEGIN {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
PROCESS{
foreach ($computer in $computername) {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members |
select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName
} # end foreach
} # end PROCESS
}
"Win12R2", "W12SUS" | get-localgroupmember
What I want is the output to look like the following and I want to flag the users in the admin group that are NOT part of our standard setup. Really I want to ignore the SAM accounts that are the domain accounts but flagging them for now works. What is happening is there is a looping through the SAM accounts to create this output. However when the machine is offline I need to note that too.
I also do NOT want to use a ValueFromPipeline but rather get a list of PC names from this command $allComputers = Get-ADComputer -Filter 'PasswordLastSet -ge $date' -properties PasswordLastSet | select Name and then use that variable as the source to loop through.
This is my revised code but I'm having issues creating a custom object to add to an array when there seems to be looping in the $group.Members |select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName
function get-localgroupmember {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="Enter PC")]
[ValidateNotNullorEmpty()]
[object]$computername = $null
)
BEGIN {
$newArray = #();
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
PROCESS{
foreach ($computer in $computername) {
If (Test-Connection -ComputerName $computer.name -Quiet -Count 1) {
try {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer.name
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members | select #{N='Server'; E={$computer.name}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName
$objComputer = [pscustomobject] #{
Server = $computer.name
Domain = $group.Members | select #{N='Domain'; E={$_.Context.Name}}
Account = $Computer.samaccountName
}
} catch {
$objComputer = [pscustomobject] #{
Server = $computer.name
Domain = "Error"
Account = "Error"
}
}
} else {
$objComputer = [pscustomobject] #{
Server = $computer.name
Domain = "Off-Line"
Account = "Off-Line"
}
} $arrayNew += $objComputer
} # end foreach
} # end PROCESS
return $arrayNew
}
$date = [DateTime]::Today.AddDays(-1)
$allComputers = Get-ADComputer -Filter 'PasswordLastSet -ge $date' -properties PasswordLastSet | select Name
get-localgroupmember -computername $allComputers | Out-GridView
To be honest I would not try to output an array object like you are. There is really no need for it. Just create each object as needed, and let it output directly (you really don't need to use return as the function will pass any output down the pipeline unless you specifically tell it otherwise, with something like Write-Host, or Out-File). Also, it looks like your input wants an object (that's pretty vague), but you are then trying to loop through that object, and use each record as the name of a PC, so what you really want for input is an array of strings. In that case change your type from [object] to [string[]]. Lastly, a good bit of your code can be simplified if you just expand the Name property when creating your $AllComputers variable. Oh, I lied, this is the last thing... Your return statement is not in a valid section of your function. It would need to be something like END{ Return $arrayNew }
Then you just have to add a list of excepted accounts to not flag, or add some logic in, or something. Honestly, your code should do pretty much everything you want it to do with a little syntax fixing. Here's based on your script, where it outputs all members of the group and flags any that arn't a local account with the name 'Administrator', and are not a domain account listed as OK (defined in the BEGIN section, currently "Domain Admins" or "Workstation Admin").
function get-localgroupmember {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="Enter PC")]
[string[]]$computername
)
BEGIN {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$OKAccounts = ("Workstation Admin","Domain Admins" | ForEach{[regex]::Escape($_)}) -join "|"
}
PROCESS{
foreach ($computer in $computername) {
If (Test-Connection -ComputerName $computer -Quiet -Count 1) {
try {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members | select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName, #{N='Flag';E={If(!(($_.Context.Name -eq $Computer -and $_.samaccountname -match "Administrator") -or ($_.context.name -ne $Computer -and $_.samaccountname -match $OKAccounts))){"X"}}}
} catch {
[pscustomobject] #{
Server = $computer
Domain = "Error"
SamAccountName = "Error"
Flag = ''
}
}
} else {
[pscustomobject] #{
Server = $computer
Domain = "Off-Line"
SamAccountName = "Off-Line"
Flag = ''
}
}
} # end foreach
} # end PROCESS
}
$date = [DateTime]::Today.AddDays(-1)
$allComputers = Get-ADComputer -Filter 'PasswordLastSet -ge $date' -properties PasswordLastSet | select -Expand Name
#$allComputers = $env:COMPUTERNAME
get-localgroupmember -computername $allComputers | Out-GridView
That should give you output something like:
Server Domain SamAccountName Flag
------ ------ -------------- ----
TMTsLab TMTsLab Administrator
TMTsLab TMTsTacoTruck.com Domain Admins
TMTsLab TMTsTacoTruck.com SomeAcct1 X
TMTsLab TMTsTacoTruck.com SomeAcct2 X
TMTsLab TMTsTacoTruck.com TMTech X
Probably better yet would be to filter out the accounts you don't want, rather than just not flag them. So change the #{N='Flag';E={If(!(($_.Context.Name -eq $Computer -and $_.samaccountname -match "Administrator") -or ($_.context.name -ne $Computer -and $_.samaccountname -match $OKAccounts))){"X"}}} bit to a Where statement, so that line would be:
$group.Members | select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName | Where { !(($_.Server -eq $_.Domain -and $_.samaccountname -match "Administrator") -or ($_.Server -ne $_.Domain -and $_.samaccountname -match $OKAccounts)) }
You'll also want to remove the Flag = '' lines from your Catch and Else scriptblocks as well. Which then the code only returns something like:
Server Domain SamAccountName
------ ------ --------------
TMTsLab TMTsTacoTruck.com SomeAcct1
TMTsLab TMTsTacoTruck.com SomeAcct2
TMTsLab TMTsTacoTruck.com TMTech
Full function code at that point:
function get-localgroupmember {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="Enter PC")]
[string[]]$computername
)
BEGIN {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$OKAccounts = ("Workstation Admin","Domain Admins" | ForEach{[regex]::Escape($_)}) -join "|"
}
PROCESS{
foreach ($computer in $computername) {
If (Test-Connection -ComputerName $computer -Quiet -Count 1) {
try {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members | select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName | Where{ !(($_.Server -ieq $_.Domain -and $_.samaccountname -match "Administrator") -or ($_.Server -ne $_.Domain -and $_.samaccountname -match $OKAccounts)) }
} catch {
[pscustomobject] #{
Server = $computer
Domain = "Error"
Account = "Error"
}
}
} else {
[pscustomobject] #{
Server = $computer
Domain = "Off-Line"
Account = "Off-Line"
}
}
} # end foreach
} # end PROCESS
}