Active logged in Users on Remote machine - powershell

I am using below script to get logged on user on remote machine . It works fine but I need to get the users those status "active"
How Can I get those active logged in users on remote machine ?
function Global:Get-LoggedOnUser {
#Requires -Version 2.0
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:COMPUTERNAME
)#End Param
Begin
{
Write-Host "`n Checking Users . . . "
$i = 0
$MyParams = #{
Class = "Win32_process"
Filter = "Name='Explorer.exe'"
ErrorAction = "Stop"
}
}#Begin
Process
{
$ComputerName | Foreach-object {
$Computer = $_
$MyParams["ComputerName"] = $Computer
try
{
$processinfo = #(Get-WmiObject #MyParams)
if ($Processinfo)
{
$Processinfo | ForEach-Object {
New-Object PSObject -Property #{
ComputerName=$Computer
LoggedOn =$_.GetOwner().User
SID =$_.GetOwnerSid().sid} } |
Select-Object ComputerName,LoggedOn,SID
}#If
}
catch
{
"Cannot find any processes running on $computer" | Out-Host
}
}#Forech-object(ComputerName)
}#Process
End
{
}#End
}#Get-LoggedOnUsers

Add a query for the Win32_ComputerSystem class:
Get-WMIObject -Class Win32_ComputerSystem -Computername $Computer | Select UserName
That'll grab the 'active' user, then you can build an object with an 'Active' boolean value.
Here's my implementation:
function Get-LoggedOnUser
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName
)
Begin
{
$users = $null
$return = #()
}
Process
{
ForEach($Computer in $ComputerName)
{
$activeUser = Get-WMIObject -class Win32_ComputerSystem -ComputerName $Computer -EA stop | select UserName
Try
{
$processinfo = #(Get-WmiObject -class win32_process -ComputerName $Computer -EA "Stop")
If ($processinfo)
{
ForEach($process in $processinfo)
{
[string[]]$users += $process.GetOwner().user| Where{($_ -ne "NETWORK SERVICE") -and ($_ -ne "LOCAL SERVICE") -and ($_ -ne "SYSTEM")}
}
If($Users)
{
ForEach($user in ($Users | Select -unique))
{
If($ActiveUser.username -like "*$user")
{
$Return += New-Object PSObject -Property #{
"User" = $user
"Active" = $true
"Computer" = $Computer
}
}
Else
{
$Return += New-Object PSObject -Property #{
"User" = $user
"Active" = $false
"Computer" = $Computer
}
}
}
}
Else
{
"There are no users logged onto $computer" | Out-Host
}
}
}
Catch
{
"Cannot find any processes running on $computer" | Out-Host
}
}
}
End
{
$Return
}
}
It is worth it to point out that the Win32_ComputerSystem username is only populated if the user is logged in locally, so anyone logged in through remote desktop won't show as 'Active'.

Related

PowerShell Script Issues with Variable Values

I am trying to write this script to restart computers only if they are Offline. The script for getting user infomration works but I cannot get the variable values for the restart portion at the bottom of the script. Does anyone have a suggestion? I am somewhat new to Powershell, but writing code. Example of my script follows:
Function Get-LoggedOnUser
{
Param
(
$ComputerName = $env:COMPUTERNAME,
$Credential
)
Function Test-RemoteRegistry
{
Param
(
[Parameter(Mandatory = $False)]
[switch]$Enable
,
[Parameter(Mandatory = $False)]
[switch]$Disable
,
[Parameter(ValueFromPipeline=$True)]
[String[]]$ComputerName = $env:COMPUTERNAME
)
Begin
{
$PipelineInput = (-not $PSBOUNDPARAMETERS.ContainsKey("ComputerName")) -and (-not $ComputerName)
Function Test ($Computer)
{
Try
{
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $Computer) | Out-Null
#20ms faster than Get-Service per computer! Not sure how to handle/check things like the firewall though...
#If we hit here without error Remote Reg is enabled.
If ($Disable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Stopped -ErrorAction Stop
Return $False
#If we hit here without error Remote Reg is now disabled.
}
Catch
{
Return $True
#If we hit here, we couldn't stop remote registry.
}
}
Else
{
Return $True
}
}
Catch
{
If ($Enable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Running -ErrorAction Stop
Return $True
#If we hit here without error Remote Reg is now enabled.
}
Catch
{
Return $False
#If we hit here, we couldn't start remote registry.
}
}
Else
{
Return $False
#If we hit here remote registry is disabled.
}
}
}
}
Process
{
If ($PipelineInput)
{
Test $_
}
Else
{
$ComputerName | ForEach-Object {
Test $_
}
}
}
}
Foreach ($Computer in $Computername)
{
$Online = $False
$User = $False
$Locked = $False
If (Test-Connection $Computer -Count 2 -Quiet)
{
$Online = $True
If ($Credential)
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer -Credential $Credential | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
Else
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
If (Test-RemoteRegistry -Enable -ComputerName $Computer)
{
If ((Get-Process logonui -ComputerName $Computer -ErrorAction SilentlyContinue) -and ($user))
{
$Locked = $True
}
}
}
$Output = New-Object PSObject
$Output | Add-Member noteproperty ComputerName $Computer
$Output | Add-Member noteproperty Online $Online
$Output | Add-Member noteproperty Username $User
$Output | Add-Member noteproperty Locked $Locked
$Output
}
}
Get-LoggedOnUser
If (($Online) -eq $False)
{Shutdown /r t 0 /m \\$Computername}
ELSE
{Write-host 'HELLO $Online $Computername'}
I just want this for a single user as I am using PDQ Inventory to roll out the script. The variables at the end of the script are $null?
Variables defined in a child scope - in which functions run by default - are never seen by the calling scope. See the conceptual about_Scopes help topic
It's best for functions to communicate values to the caller via their output ("return value"), which you're function is already doing: it outputs objects whose properties contain the values of interest.
Therefore:
Get-LoggedOnUser |
ForEach-Object { # Loop over all output objects
# Refer to the object at hand via the automatic $_ variable.
# Note the use of "..." (expandable strings) so as to support
# expansion (string interpolation).
if (-not $_.Online) { Shutdown /r t 0 /m "\\$($_.ComputerName)" }
else { "HELLO $($_.Online) $($_.ComputerName)" }
}

How do you make a function that only takes strings, take variables in Powershell?

I want to run a function in Powershell called Get-OSArchitecture which tells me whether a computer has a 32bit or 64bit system when you give it a domain name. However, it only accepts strings such as "SALES-DENNY" and not variables with stored strings such as $string1. I've played around with something called Out-String but this function is really stubborn with getting strings and nothing to do with variables.
The following code is for getting the global Get-OSArchitecture function:
function global:Get-OSArchitecture {
#Requires -Version 2.0
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false,
Position=1,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:COMPUTERNAME
)#End Param
Begin
{
Write-Verbose "Retrieving Computer Info . . ."
}
Process
{
$ComputerName | foreach {
$ErrorActionPreference = 0
$Computer = $_
$Windir,$OSArchitecture,$OSVersion = Get-WmiObject -class Win32_OperatingSystem -ComputerName $_ |
foreach {$_.WindowsDirectory,$_.OSArchitecture,$_.Version}
$SysDrive = ($Windir -split ":")[0] + "$"
# $OSVersion[0]
# $OSArchitecture is only suppored on OSVersion -ge 6
# I was going to test for that, however now I just test if $OSArchitecture -eq $True
Write-Verbose "Operating System version on $Computer is: $OSVersion"
if ($OSArchitecture)
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture=$OSArchitecture
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
else
{
# check the program files directory
write-verbose "System Drive on $Computer is: $SysDrive"
$x64 = "\\$Computer\" + $SysDrive + "\Program Files (x86)"
if (test-path ("\\$Computer\" + $SysDrive))
{
if (test-path $x64)
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture="64-bit"
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
elseif (!(test-path $x64))
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture="32-bit"
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
}
else {"Something wrong determining the System Drive"}
}
} | select Hostname,OSArchitecture,SysDrive,WinDir,OSVersion
}#Process
End
{
}#End
}#Get-OSArchitecture
My problem begins below.
$string1 = "SALES-DENNY"
Get-OSArchitecture $string1
The above fails.
The below works.
Get-OSArchitecture "SALES-DENNY"
I expect the function to give out the correct architecture of the computer with the name "SALES-DENNY" but if I don't put it in as a string I always get a blank result.
Although it should not matter if you give the computername as hardcoded string or as a name or IP in a variable, I do believe you could improve the function by not testing the Program Files (x86) directory.
Instead, there are two other WMI functions you can rely on to get the 'bitness' of the OS:
function Get-OSArchitecture {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string[]]$ComputerName = $env:COMPUTERNAME
)
process {
foreach ($computer in $ComputerName) {
Write-Verbose "Retrieving info for computer '$computer'"
$info = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer |
Select-Object #{Name = 'HostName'; Expression = { $_.PSComputerName}},
OSArchitecture,
#{Name = 'SysDrive'; Expression = { '{0}$' -f ($_.SystemDrive).Substring(0,1) }},
#{Name = 'WinDir'; Expression = { $_.WindowsDirectory}},
#{Name = 'OSVersion'; Expression = { $_.Version }}
if ($info.OSArchitecture) {
$info.OSArchitecture = '{0}-bit' -f ($info.OSArchitecture -replace '\D+','')
}
else {
$info.OSArchitecture = '{0}-bit' -f (Get-WmiObject -Class Win32_Processor -ComputerName $computer).AddressWidth
# or do:
# $info.OSArchitecture = '{0}-bit' -f (((Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer).SystemType -replace '\D+', '') -replace '86', '32')
}
# emit info
$info
}
}
}
Hope that helps

Get-WmiObject deprecated now what?

I am trying to get the following function to work within PowerShell 6.0.2, however apparently Get-WmiObject has been deprecated. Can anyone help me figure out how to replace it with Get-CimInstance which has replaced it?
Get-WmiObject is within the PROCESS area of the code below.
Complete function code provided in case someone is interested.
function Get-DiskFree
{
[CmdletBinding()]
param
(
[Parameter(Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('hostname')]
[Alias('cn')]
[string[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Position=1,
Mandatory=$false)]
[Alias('runas')]
[System.Management.Automation.Credential()]$Credential =
[System.Management.Automation.PSCredential]::Empty,
[Parameter(Position=2)]
[switch]$Format
)
BEGIN
{
function Format-HumanReadable
{
param ($size)
switch ($size)
{
{$_ -ge 1PB}{"{0:#.#'P'}" -f ($size / 1PB); break}
{$_ -ge 1TB}{"{0:#.#'T'}" -f ($size / 1TB); break}
{$_ -ge 1GB}{"{0:#.#'G'}" -f ($size / 1GB); break}
{$_ -ge 1MB}{"{0:#.#'M'}" -f ($size / 1MB); break}
{$_ -ge 1KB}{"{0:#'K'}" -f ($size / 1KB); break}
default {"{0}" -f ($size) + "B"}
}
}
$wmiq = 'SELECT * FROM Win32_LogicalDisk WHERE Size != Null AND DriveType >= 2'
}
PROCESS
{
foreach ($computer in $ComputerName)
{
try
{
if ($computer -eq $env:COMPUTERNAME)
{
$disks = Get-WmiObject -Query $wmiq `
-ComputerName $computer -ErrorAction Stop
}
else
{
$disks = Get-WmiObject -Query $wmiq `
-ComputerName $computer -Credential $Credential `
-ErrorAction Stop
}
if ($Format)
{
# Create array for $disk objects and then populate
$diskarray = #()
$disks | ForEach-Object { $diskarray += $_ }
$diskarray | Select-Object #{n='Name';e={$_.SystemName}},
#{n='Vol';e={$_.DeviceID}},
#{n='Size';e={Format-HumanReadable $_.Size}},
#{n='Used';e={Format-HumanReadable `
(($_.Size)-($_.FreeSpace))}},
#{n='Avail';e={Format-HumanReadable $_.FreeSpace}},
#{n='Use%';e={[int](((($_.Size)-($_.FreeSpace))`
/($_.Size) * 100))}},
#{n='FS';e={$_.FileSystem}},
#{n='Type';e={$_.Description}}
}
else
{
foreach ($disk in $disks)
{
$diskprops = #{'Volume'=$disk.DeviceID;
'Size'=$disk.Size;
'Used'=($disk.Size - $disk.FreeSpace);
'Available'=$disk.FreeSpace;
'FileSystem'=$disk.FileSystem;
'Type'=$disk.Description
'Computer'=$disk.SystemName;}
# Create custom PS object and apply type
$diskobj = New-Object -TypeName PSObject `
-Property $diskprops
$diskobj.PSObject.TypeNames.Insert(0,'BinaryNature.DiskFree')
Write-Output $diskobj
}
}
}
catch
{
# Check for common DCOM errors and display "friendly" output
switch ($_)
{
{ $_.Exception.ErrorCode -eq 0x800706ba } `
{ $err = 'Unavailable (Host Offline or Firewall)';
break; }
{ $_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } `
{ $err = 'Access denied (Check User Permissions)';
break; }
default { $err = $_.Exception.Message }
}
Write-Warning "$computer - $err"
}
}
}
END {}
}
Below is the PowerShell commands that I will run after loading the function, taken from this site: http://binarynature.blogspot.com/2010/04/powershell-version-of-df-command.html
$cred = Get-Credential 'example\administrator'
$servers = 'dc01','db01','exch01','sp01'
Get-DiskFree -Credential $cred -cn $servers -Format |
? { $_.Type -like '*fixed*' } |
select * -ExcludeProperty Type |
Out-GridView -Title 'Windows Servers Storage Statistics'
As EBGreen stated, this can be resolved by changing Get-WmiObject to Get-CimInstance. There are only two lines in that function that need rewriting:
Current (using Get-WmiObject)
$disks = Get-WmiObject -Query $wmiq -ComputerName $computer -ErrorAction Stop
$disks = Get-WmiObject -Query $wmiq -ComputerName $computer -Credential $Credential -ErrorAction Stop
Changed (using Get-CimInstance)*
$disks = Get-CimInstance -Query $wmiq -ComputerName $computer -ErrorAction Stop
$disks = Invoke-Command -ArgumentList $wmiq { param($wmiq) Get-CimInstance -Query $wmiq } -ComputerName $computer -Credential $Credential -ErrorAction Stop | Select-Object DeviceID, DriveType, ProviderName, FreeSpace, Size, VolumeName
Here is the full function with these changes already made (and cleaned up a bit to my liking). I can confirm that it is working on PowerShell Core v6.1.2
function Get-DiskFree {
[CmdletBinding()]
param (
[Parameter(Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('cn')]
[string[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Position=1, Mandatory=$false)]
[Alias('cr')]
[System.Management.Automation.Credential()]
$Credential = [System.Management.Automation.PSCredential]::Empty,
[Parameter(Position=2)]
[Alias('f')]
[switch]$Format
)
begin {
$ErrorActionPreference = "Stop"
function Format-HumanReadable {
param (
$size
)
switch ($size) {
{$_ -ge 1PB}
{"{0:#.#'P'}" -f ($size / 1PB); break}
{$_ -ge 1TB}
{"{0:#.#'T'}" -f ($size / 1TB); break}
{$_ -ge 1GB}
{"{0:#.#'G'}" -f ($size / 1GB); break}
{$_ -ge 1MB}
{"{0:#.#'M'}" -f ($size / 1MB); break}
{$_ -ge 1KB}
{"{0:#'K'}" -f ($size / 1KB); break}
default
{"{0}" -f ($size) + "B"}
}
}
$wmiq = 'SELECT * FROM Win32_LogicalDisk WHERE Size != Null AND DriveType >= 2'
}
process {
foreach ($computer in $ComputerName) {
try {
if ($computer -eq $env:COMPUTERNAME) {
$disks = Get-CimInstance -Query $wmiq -ComputerName $computer
}
else {
$disks = Invoke-Command -ArgumentList $wmiq { param($wmiq) Get-CimInstance -Query $wmiq } -ComputerName $computer -Credential $Credential `
| Select-Object DeviceID, DriveType, ProviderName, FreeSpace, Size, VolumeName
}
if ($Format) {
# Create array for $disk objects and then populate
$diskarray = #()
$disks | ForEach-Object { $diskarray += $_ }
$diskarray | Select-Object
#{Name='Name'; Expression={$_.SystemName}},
#{Name='Vol'; Expression={$_.DeviceID}},
#{Name='Size'; Expression={Format-HumanReadable $_.Size}},
#{Name='Used'; Expression={Format-HumanReadable (($_.Size)-($_.FreeSpace))}},
#{Name='Avail'; Expression={Format-HumanReadable $_.FreeSpace}},
#{Name='Use%'; Expression={[int](((($_.Size)-($_.FreeSpace))/($_.Size) * 100))}},
#{Name='FS'; Expression={$_.FileSystem}},
#{Name='Type'; Expression={$_.Description}}
}
else {
foreach ($disk in $disks) {
$diskprops = #{
'Volume'=$disk.DeviceID;
'Size'=$disk.Size;
'Used'=($disk.Size - $disk.FreeSpace);
'Available'=$disk.FreeSpace;
'FileSystem'=$disk.FileSystem;
'Type'=$disk.Description
'Computer'=$disk.SystemName;
}
# Create custom PS object and apply type
$diskobj = New-Object -TypeName PSObject -Property $diskprops
$diskobj.PSObject.TypeNames.Insert(0,'BinaryNature.DiskFree')
Write-Output $diskobj
}
}
}
catch {
# Check for common DCOM errors and display "friendly" output
switch ($_) {
{ $_.Exception.ErrorCode -eq 0x800706ba }
{$err = 'Unavailable (Host Offline or Firewall)'; break}
{ $_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' }
{$err = 'Access denied (Check User Permissions)'; break}
default
{$err = $_.Exception.Message}
}
Write-Warning "$computer - $err"
}
}
}
end {
}
}

get local admin users with password age

I am working on once assignment where want to get a list of local Windows admin users with X password age. Got below function for local admin users and other one for age. Please help me integrate these.
I have below command can work with users list to fetch details from specific groups and hostnames.
Get-Content -Path "D:\Groups.txt" | ForEach-Object {
Get-GroupMember -ComputerName (Get-Content -Path "D:\servers.txt") -LocalGroup $_
} | Export-Csv -Path D:\Getgroupmembers_$(Get-Date -Format ddMMyyyy).csv -NoTypeInformation
List of users:
function Get-GroupMember {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[Alias('Group')]
[string]$LocalGroup,
[Alias('CN','Computer')]
[string[]]$ComputerName = '.'
)
foreach ($Computer in $ComputerName) {
Write-Verbose "Checking membership of localgroup: '$LocalGroup' on $Computer"
try {
([adsi]"WinNT://$Computer/$LocalGroup,group").psbase.Invoke('Members') | ForEach-Object {
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $Computer
LocalGroup = $LocalGroup
Member = $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
}
}
Write-Verbose "Successfully checked membership of localgroup: '$LocalGroup' on $Computer"
} catch {
Write-Warning $_
}
}
}
TO check Password age we can use below code and we need to integrate these two using one command:
function Get-PwdAge {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false,
Position=1,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false)]
[String]$Usr,
[Switch]$All
)
$filter = "(&(objectCategory=person)(objectClass=user)(name=$Usr))"
if ($All) {
$filter = '(&(objectCategory=person)(objectClass=user))'
}
$root = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
$searcher = New-Object System.DirectoryServices.DirectorySearcher $filter
$SearchRoot = $root.defaultNamingContext
$searcher.SearchRoot = "LDAP://CN=Users,$SearchRoot"
$searcher.SearchScope = 'SubTree'
$searcher.SizeLimit = 0
$searcher.PageSize = 1000
$searcher.FindAll() | ForEach-Object {
$account = $_.GetDirectoryEntry()
$pwdset = [DateTime]::FromFileTime($_.Properties.Item("pwdLastSet")[0])
$age = (New-TimeSpan $pwdset).Days
$info = 1 | Select-Object Name, Login, AgeInDays, LastSet
$info.Name = $account.DisplayName[0]
$info.Login = $account.SamAccountName[0]
$info.AgeInDays = $age
$info.LastSet = $pwdset
$info
}
}
Param
(
[Parameter(Position=0,Mandatory=$false)]
[ValidateNotNullorEmpty()]
[Alias('cn')][String[]]$ComputerName=$Env:COMPUTERNAME,
[Parameter(Position=1,Mandatory=$false)]
[Alias('un')][String[]]$AccountName,
[Parameter(Position=2,Mandatory=$false)]
[Alias('cred')][System.Management.Automation.PsCredential]$Credential
)
$Obj = #()
$now = Get-Date
Foreach($Computer in $ComputerName)
{
If($Credential)
{
$AllLocalAccounts = Get-WmiObject -Class Win32_UserAccount -Namespace "root\cimv2" `
-Filter "LocalAccount='$True'" -ComputerName $Computer -Credential $Credential -ErrorAction Stop
}
else
{
$AllLocalAccounts = Get-WmiObject -Class Win32_UserAccount -Namespace "root\cimv2" `
-Filter "LocalAccount='$True'" -ComputerName $Computer -ErrorAction Stop
}
$Obj = $AllLocalAccounts | ForEach-Object {
$user = ([adsi]"WinNT://$computer/$($_.Name),user")
$pwAge = $user.PasswordAge.Value
$maxPwAge = $user.MaxPasswordAge.Value
$pwLastSet = $now.AddSeconds(-$pwAge)
New-Object -TypeName PSObject -Property #{
'Account Name' = $_.Name
'Disabled' = $_.Disabled
'Password Expires' = $_.PasswordExpires
'Password Last Set' = $pwLastSet
'Password Expiry Date' = $now.AddSeconds($maxPwAge - $pwAge)
'Password Required' = $_.PasswordRequired
'Domain' = $_.Domain
'Password Age' = ($now - $pwLastSet).Days
}
}
If($AccountName)
{
Foreach($Account in $AccountName)
{
$Obj|Where-Object{$_.Name -like "$Account"}
}
}
else
{
$Obj
}
}

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
}