PowerShell Script Issues with Variable Values - powershell

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)" }
}

Related

Detect SMB1 version via powershell for all OSes

My workflow:
check if server is pingable
find if they are domain connected or not and perform a task accordingly. if Operating system 2012 and/or R2 ,2016 or 2019 newer OSes then I will run Get-SmbServerConfiguration cmdlet. if machine is not a part of default domain then else block will run.
if Operating system 2003 or 2008 oldest OSes then I will run Get-Wmi cmdlet. if machine is not a part of default domain then else block will run.
Finally , I will concentanate $results variable.
My question is :
1- How can we get remotely regedit value for 2003 or 2008 oldest OSes IS NOT a part of default domain insie else block?
Also , Condition will be like below.
if SMB1 value is "0" then result will be `false`
if SMB1 value is "1" then result will be `true`
if SMB1 value is not exist then result will be `not exist value`
2- How can I create object properties $SMBAudit variable ? because , I will concentanate all outputs inside $results variable.
$reg = [wmiclass]"\\$computer\root\default:StdRegProv"
$SMBAudit = $reg.GetStringValue($basekey, $subkey, $value).sValue
My desired output :
Computername,SMB1Enabled
Host01,True
Host02,False
I will write so far a script like below. but I am stucking somethings.
Script :
# Computer List
$allComputers = Get-Content .\path\to\computers.txt
read-host -assecurestring | convertfrom-securestring | out-file C:\mysecurestring_domain.txt
read-host -assecurestring | convertfrom-securestring | out-file C:\mysecurestring_local.txt
# Create empty array of results
$Results = #()
# Loop through computers
foreach($computer in $allComputers) {
# check if server is pingable before running the query on the server
if (Test-Connection $computer -Count 1 -Quiet) {
Write-Host "`n`n$computer is online!" -BackgroundColor Green -ForegroundColor Black
}
if(Get-ADComputer -Filter {Name -eq $computer -and OperatingSystem -notlike '*Windows*Server*2003*' -and OperatingSystem -notlike '*Windows*Server*2008*'})
{
#"machine $_ is a part of default domain"
# The command we want to run
$username = "domain01\admin01"
$password = Get-Content 'C:\mysecurestring_domain.txt' | ConvertTo-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
$SMB = Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {Get-SmbServerConfiguration | Select EnableSMB1Protocol }
# Create properties
$Properties = #{
# Populate the properties "Computername" and "SMB1Enabled" with variables
Computername = $Computer
SMB1Enabled = $SMB.EnableSMB1Protocol
}
# Add the properties to the result for each object
$Results += New-Object psobject -Property $Properties
}
else
{
#"machine $_ IS NOT a part of default domain"
$username = "localadmin01"
$password = Get-Content 'C:\mysecurestring_local.txt' | ConvertTo-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
$SMB = Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {Get-SmbServerConfiguration | Select EnableSMB1Protocol }
# Create properties
$Properties = #{
# Populate the properties "Computername" and "SMB1Enabled" with variables
Computername = $Computer
SMB1Enabled = $SMB.EnableSMB1Protocol
}
# Add the properties to the result for each object
$Results += New-Object psobject -Property $Properties
}
# Oldest OSes
if(Get-ADComputer -Filter {Name -eq $computer -and OperatingSystem -notlike '*Windows*Server*2012*' -and OperatingSystem -notlike '*Windows*Server*2016*' -and OperatingSystem -notlike '*Windows*Server*2019*'})
{
#"machine $_ is a part of default domain"
# The command we want to run
<# HKEY_CLASSES_ROOT (2147483648 (0x80000000))
HKEY_CURRENT_USER (2147483649 (0x80000001))
HKEY_LOCAL_MACHINE (2147483650 (0x80000002))
HKEY_USERS (2147483651 (0x80000003))
HKEY_CURRENT_CONFIG (2147483653 (0x80000005))
#>
$basekey = [uint32]'0x80000002'
$subkey = 'SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$value = 'SMB1'
$reg = [wmiclass]"\\$computer\root\default:StdRegProv"
$SMBAudit = $reg.GetStringValue($basekey, $subkey, $value).sValue
}
else
{
#"machine $_ IS NOT a part of default domain"
}
# Output
$Results | Select-Object Computername, SMB1Enabled | Out-File -Filepath c:\temp\smb1-computers.txt
I think you are over complicating this and although not tested by me, you could try this:
# Computer List
$allComputers = Get-Content '.\path\to\computers.txt'
# get credentials for domain-joined machines and for local machines
$domainCred = Get-Credential -UserName "domain01\admin01" -Message "Please enter the DOMAIN password"
$localCred = Get-Credential -UserName "localadmin01" -Message "Please enter the LOCAL password"
# loop through the list of computers and collect output in variable $Results
$Results = foreach($computer in $allComputers) {
# check if server is pingable before running the query on the server
if (Test-Connection -ComputerName $computer -Count 1 -Quiet) {
Write-Host "$computer is online!" -BackgroundColor Green -ForegroundColor Black
$server = Get-ADComputer -Filter "Name -eq '$computer'" -Properties OperatingSystem -ErrorAction SilentlyContinue
# if domain joined, use $domainCred, otherwise $localCred
if ($server) {
$cred = $domainCred
$version = ([regex]'Windows Server (\d+)').Match($server.OperatingSystem).Groups[1].Value
}
else {
$cred = $localCred
$info = Get-WmiObject -ComputerName $computer -Credential $cred -Class Win32_OperatingSystem
$version = ([regex]'Windows Server (\d+)').Match($info.Caption).Groups[1].Value
}
if ($version -eq '2003') {
# try reading the registry
try {
$RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer)
$RegKey = $RegBase.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
$SMB = $RegKey.GetValue("SMB1")
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = ($null -eq $SMB -or [int]$SMB -eq 1) }
}
catch {
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = 'Could not read Remote Registry' }
}
finally {
if ($RegBase) { $RegBase.Close() }
if ($RegKey) { $RegKey.Close() }
}
}
elseif ($version -eq '2008') {
# Older OS
try {
# try via WinRM
$SMB = Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters' -Name SMB1
} -ErrorAction Stop
# output an object
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = ($null -eq $SMB -or [int]$SMB -eq 1) }
}
catch {
# try reading the registry
try {
$RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer)
$RegKey = $RegBase.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
$SMB = $RegKey.GetValue("SMB1")
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = ($null -eq $SMB -or [int]$SMB -eq 1) }
}
catch {
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = 'Could not read Remote Registry' }
}
finally {
if ($RegBase) { $RegBase.Close() }
if ($RegKey) { $RegKey.Close() }
}
}
}
else {
# Newer OS
$SMB = Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock { Get-SmbServerConfiguration | Select-Object EnableSMB1Protocol }
# output an object
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = $SMB.EnableSMB1Protocol }
}
}
else {
Write-Warning "Computer $computer is off-line"
# output an object anyway, so that in the CSV it is known that the computer didn't ping
[PsCustomObject]#{ ComputerName = $computer; SMB1Enabled = 'Off-Line' }
}
}
# Output on screen
$Results | Format-Table -AutoSize
# Output to CSV file
$Results | Export-Csv -Path 'c:\temp\smb1-computers.csv' -NoTypeInformation -UseCulture

Can't assign value to a variable inside of Invoke-Command

It seems to be strange but I can't assign a value to variable inside of Invoke-Command. Here is the code below but when print out $targetComputerPath it's simply empty. What's wrong?
foreach ($item in $computersPath){
$computername = $item.Name
$username = $item.UserID
Write-Host computer $computername and user $username
if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue)
{
if ($((Get-Service WinRM -ComputerName $computername).Status) -eq "stopped")
{
(Get-Service WinRM -ComputerName $computername).Start()
}
Invoke-Command -ComputerName $computername -ScriptBlock {
if ($((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").ReleaseId) -eq "1903" )
{
$targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
write-host "1903"
}
else
{
$targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
write-host "something else"
}
}
}
write-host $targetComputerPath
}
The point of WinRM is that you take a script block, and execute it on a different machine.
None of the variables you define in the host script will be available on the remote machine.
This becomes more apparent when you separate the "task", a.k.a the script block, from the Invoke-Command, like this:
$task = {
$version = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
if ($version.ReleaseId -eq "1903") {
# note that `$username` cannot be available here, it's never been defined!
return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
} else {
return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
}
}
foreach ($item in $computersPath) {
$computername = $item.Name
$username = $item.UserID
Write-Host computer $computername and user $username
if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue) {
$winrm = Get-Service WinRM -ComputerName $computername
if ($winrm.Status -eq "stopped") { $winrm.Start() }
$targetComputerPath = Invoke-Command -ComputerName $computername -ScriptBlock $task
Write-Host "The machine returned: $targetComputerPath"
}
}
As you can see, you can return values from the script block and they will be available as the return value of Invoke-Command.
If you want to pass arguments to your script block, this thread talks about that: How do I pass named parameters with Invoke-Command?

Accessible a powershell variable inside a function / workflow

I am working on a Powershell script with a function and a Workflow. Unfortunately, I was unable to access variables inside the function. Here is an example :
$location = "c:\temp"
function PingComputer
{
Param($ip)
$res = Test-Connection -ComputerName $ip -quiet -Count 1
If ($res -eq "true")
{
Try
{
#Some tasks if pings are ok
#For example : copy-item -path $location -destination $dest -force -recurse
}
Catch
{
#Catch exceptions
}
}
Else
{
#Ping fail
}
}
workflow parallelPingCOmputer {
Param($ips)
$i=0
foreach -parallel($ip in $ips)
{
PingComputer($ip)
$workflow:i++
$count = $ips.Count
InlineScript {
#write-host "$using:i : " $using:ips.count " : $using:ips "
Write-Progress -Activity "Poste : $using:ip" -Status "Postes effectués : $using:i sur $using:count" -PercentComplete (($using:i / $using:Count) * 100)
sleep -s 1
}
}
}
$request = parallelPingComputer -ips $ip_list | Select-object date, computer, result | out-gridview
This is a simplified version of my current script. But, as you can see, the variable $location can't be accessed inside my function PingComputer. I tried to modify its scope as global or script, but nothing works.
The message I get with the copy-item is "path is null"... How can I make my variable accessible ?
If you want to reuse the function, just copy the function inside the workflow and keep it outside. Else, copy the function inside the workflow and remove the one outside like the code below. It could solve your problem without using a function inside the workflow.
I made an example on my Github :
Workflow Get-Ping{
Param(
[Parameter(Mandatory = $true)][string[]]$Computers
)
Foreach -Parallel ($computer in $Computers){
$ping = $null
$version = $null
if(Test-Connection -ComputerName $computer -Count 1 -Quiet){
$ping = "Online"
$version = Get-WmiObject -Namespace "root\cimv2" -Class "Win32_OperatingSystem" -PSComputerName $computer | select Version
}
else{
$ping = "Offline"
}
#if no gwmi use -ComputerName $computer
$arrayResults = New-Object -Type PSObject -Property #{
Hostname = $computer
Ping = $ping
Version = $version.Version
}
return($arrayResults)
}
}
$computers = Get-Content ".\Computers.txt"
Write-Host "$($computers.Count) computers found" -ForegroundColor Green
Get-Ping -Computers $computers | Select-Object Hostname, Ping, Version | Sort-Object Hostname | Out-GridView -Title "Powershell Workflow - Ping"

Getting output from background jobs

I created the following script to reset the password of the local admin account on all machines in a specific host file. This script functions properly and provides a useful output, but it is slow as it only does one machine at a time.
# function to convert a secure string to a standard string
function ConvertTo-String {
param(
[System.Security.SecureString] $secureString
)
$marshal = [System.Runtime.InteropServices.Marshal]
try {
$intPtr = $marshal::SecureStringToBSTR($secureString)
$string = $marshal::PtrToStringAuto($intPtr)
}
finally {
if($intPtr) {
$marshal::ZeroFreeBSTR($intPtr)
}
}
$string
}
$clients = Get-Content -Path C:\scripts\utilities\hostnames_online.txt
$adminUser = "Administrator"
# prompt for password and confirm
do {
$ss1 = Read-Host "Enter new password" -AsSecureString
$ss2 = Read-Host "Enter again to confirm" -AsSecureString
# compare strings - proceed if same - prompt again if different
$ok = (ConvertTo-String $ss1) -ceq (ConvertTo-String $ss2)
Write-Host "Passwords match"
if(-not $ok) {
Write-Host "Passwords do not match"
}
}
until($ok)
# set password variable to string value
$adminPassword = ConvertTo-String $ss1
# setup job to reset password on each client
foreach($client in $clients) {
$status = "OFFLINE"
$isOnline = "OFFLINE"
if((Test-Connection -ComputerName $client -Quiet -Count 1 -Delay 1) -eq $true) {
$isOnline = "ONLINE"
}
# change the password
try {
$localAdminAccount = [adsi]"WinNT://$client/$adminuser,user"
$localAdminAccount.SetPassword($adminPassword)
$localAdminAccount.SetInfo()
Write-Verbose "Password change completed successfully"
}
catch {
$status = "FAILED"
Write-Verbose "Failed to change password"
}
# create psobject with system info
$obj = New-Object -TypeName PSObject -Property #{
ComputerName = $client
Online = $isOnline
ChangeStatus = $status
}
$obj | Select computerName, Online, changeStatus | Out-File -FilePath C:\test.txt -Append
if($status -eq "FAILED" -or $isOnline -eq "OFFLINE") {
$stream.writeline("$client -t $status")
}
}
$adminPassword = " "
Write-Verbose "Complete"
Invoke-Item C:\test.txt
To make the script run faster, I set it up to use background jobs so it could run on multiple clients at once. However, now I get no output in my text files. The new script is below. Lines 43 and 79-89 are the changes. What changes do I need to make to this script provide the output it does in the first version? I've tried a number of other ways to get the output than what I currently have on line 89.
# function to convert a secure string to a standard string
function ConvertTo-String {
param(
[System.Security.SecureString] $secureString
)
$marshal = [System.Runtime.InteropServices.Marshal]
try {
$intPtr = $marshal::SecureStringToBSTR($secureString)
$string = $marshal::PtrToStringAuto($intPtr)
}
finally {
if($intPtr) {
$marshal::ZeroFreeBSTR($intPtr)
}
}
$string
}
$clients = Get-Content -Path C:\scripts\utilities\hostnames_online.txt
$adminUser = "Administrator"
# prompt for password and confirm
do {
$ss1 = Read-Host "Enter new password" -AsSecureString
$ss2 = Read-Host "Enter again to confirm" -AsSecureString
# compare strings - proceed if same - prompt again if different
$ok = (ConvertTo-String $ss1) -ceq (ConvertTo-String $ss2)
Write-Host "Passwords match"
if(-not $ok) {
Write-Host "Passwords do not match"
}
}
until($ok)
# set password variable to string value
$adminPassword = ConvertTo-String $ss1
# setup job to reset password on each client
#-----------------------------------------------------------------------------------------
$scriptBlock = {
#-----------------------------------------------------------------------------------------
foreach($client in $clients) {
$status = "OFFLINE"
$isOnline = "OFFLINE"
if((Test-Connection -ComputerName $client -Quiet -Count 1 -Delay 1) -eq $true) {
$isOnline = "ONLINE"
}
# change the password
try {
$localAdminAccount = [adsi]"WinNT://$client/$adminuser,user"
$localAdminAccount.SetPassword($adminPassword)
$localAdminAccount.SetInfo()
Write-Verbose "Password change completed successfully"
}
catch {
$status = "FAILED"
Write-Verbose "Failed to change password"
}
# create psobject with system info
$obj = New-Object -TypeName PSObject -Property #{
ComputerName = $client
Online = $isOnline
ChangeStatus = $status
}
$obj | Select computerName, Online, changeStatus | Out-File -FilePath C:\test.txt -Append
if($status -eq "FAILED" -or $isOnline -eq "OFFLINE") {
$stream.writeline("$client -t $status")
}
}
}
#-----------------------------------------------------------------------------------------
Get-Job | Remove-Job -Force
Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset
Get-Job
While(Get-Job -State "Running") {
Start-Sleep -m 10
}
Receive-Job -name AdminPWReset | Out-File C:\test2.txt
#-----------------------------------------------------------------------------------------
$adminPassword = " "
Write-Verbose "Complete"
Invoke-Item C:\test.txt
Invoke-Item C:\test2.txt
You don't receive output, because you moved your entire loop into the scriptblock:
$scriptBlock = {
foreach ($client in $clients) {
...
}
}
but initialized your client list ($clients) outside the scriptblock, so that the variable $clients inside the scriptblock is a different (empty) variable since it's in a different scope. Because of that your job is iterating over an empty list, and is thus not producing any output.
To be able to use the client list inside the scriptblock you'd have to use the using: scope modifier:
$scriptBlock = {
foreach ($client in $using:clients) {
...
}
}
or pass the client list into the scriptblock as an argument:
$scriptBlock = {
foreach ($client in $args[0]) {
...
}
}
Start-Job -ScriptBlock $scriptBlock -ArgumentList $clients
As I can see from your code you're trying to do the latter:
Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset
However, that doesn't work for two reasons:
In the context where you run Start-Job there is no current object, so $_ is empty and nothing is passed into the scriptblock.
The scriptblock neither has a Param() block, nor does it use the automatic variable $args, so even if you passed an argument into the scriptblock it would never be used.
With that said, you don't want to pass $clients in the first place, because even if it worked, it wouldn't speed up anything as the entire client list would still be processed sequentially. What you actually want to do is to process the list in parallel. For that you must put just the tests into the scriptblock and then start one job for each client in a loop:
$scriptBlock = {
Param($client)
$status = "OFFLINE"
$isOnline = "OFFLINE"
if (Test-Connection -Computer $client -Quiet -Count 1 -Delay 1) {
$isOnline = "ONLINE"
}
...
}
foreach ($client in $clients) {
Start-Job -Name AdminPWReset -ScriptBlock $scriptBlock -ArgumentList $client
}
while (Get-Job -State "Running") {
Start-Sleep -Milliseconds 100
}
Receive-Job -Name AdminPWReset | Out-File C:\test2.txt
Remove-Job -Name AdminPWReset
Note that if you have a great number of target hosts you may want to use a job queue, so that you don't have hundreds of jobs running in parallel.

Active logged in Users on Remote machine

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'.