I am having some problems with updating data in a list from the results of a hash table. I am 99% sure it is due to the lack of understanding of what i am doing.
I am generating a $list of servers from a CSV. the CSV contains Servername, domain, description, plus some additional blank columns for use later.
what i am trying to do in a nutshell: i need to pull the down processes from a list of remote servers. to do this i am throwing each server from the list and function into its own runspace, the Hashtable is updating as expected. But i can not update the original $list i have.
here is my code:
Function OpenFile ($FilePath) {
$OFDiag = new-object system.windows.forms.openfiledialog
$OFDiag.filter = 'CSV (*.csv) | *.csv'
$OFDiag.ShowDialog() | out-null
$OFDiag.filename
}
# Create Primary Variables
$FilePath = OpenFile
$list = (get-content $FilePath) -replace '\(',' -' #ALL Servers and Groups need to remove parenthesis
$list = $list -replace '\)' #finish up removing the parenthesis
$list = $list -replace ' Or WorkGroup'
$list = convertFrom-CSV $list | select 'Name', 'Computer_Description', 'Domain' #Need to convert the list into a CSV formatted table.
$list = $list | sort Name
$list | Add-Member -NotePropertyName 'LastReboot' -NotePropertyValue $null
$list | Add-Member -NotePropertyName 'LastDeployment' -NotePropertyValue $null
$list | Add-Member -NotePropertyName 'RebootStatus' -NotePropertyValue $null
$list | Add-Member -NotePropertyName 'DownProcess' -NotePropertyValue $null
$list | Add-Member -NotePropertyName 'EnabledStatus' -NotePropertyValue $null
$list | Add-Member -NotePropertyName 'RDP' -NotePropertyValue $null
$list | Add-Member -NotePropertyName 'SchedTask' -NotePropertyValue $null
$servers = $list | %{$_.Name} | sort #ALL SERVERS - ONLY Servernames
$ServProSel = {
#
# Checks for Running Services and Processes.
# This Makes a determination as to what service/process groups should be checked.
# The Information as to what processes to look for are sent to the ProSer_Check function
# information from there is sent to the ServerStatus Tab
#
#Write-Host 'starting ServerProSel'
Param ($computer,$cred,$grpName,$hash)
#$cred = $(get-Variable "$Domain" -valueOnly)
$ck =#{} #$(Get-Variable -name "SCP_$serName" -ValueOnly)
Function ProSer_Check {
# This is the actual function that is run on the remote system to check
# for processes and services.
param ( [array] $Prcs,
[string] $Computer )
$script:chkres =#()
foreach ($p in $Prcs){
$script:res = Get-Process -name $p -ErrorAction SilentlyContinue
if (!$res) {
$chk = "$p -DOWN`r`n"
$chkres += $chk
}
}
if ($chkres.count -eq 0){
$chkres = "All Processes Up"}
Return $chkres
}
switch -Regex ($grpName){
'Demonstration' {
$Prcs = #('Process.Service'); break}
'Historian' {
$Prcs =#('Process.Service'); break}
'Models' {
$Prcs =#('UpdaterServer'); break}
'Inflictor' {
$Prcs =#('Automation.EngineService','Automation.Manager.Service','Automation.SmfLauncher','postgres','Redundancy.Server','WatchDog.Service'); break}
'Simulator' {
$Prcs =#('proc','moni','server','serve','clerk','web'); break}
'WebServer' {
$Prcs =#('w3wp','VShell'); break}
default {
$Prcs =#('svchost'); break}
}
$R = invoke-command -credential $cred -computername $Computer -scriptblock ${function:ProSer_Check} -ArgumentList $Prcs,$Computer
$hash[$Computer]=([string]$R)
}
$Script:runspaces = New-Object System.Collections.ArrayList
$Global:hash = [hashtable]::Synchronized(#{})
$global:sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$global:runspacepool = [runspacefactory]::CreateRunspacePool(1, 10, $sessionstate, $Host)
$global:runspacepool.Open()
Function SendToRunSpace {
$function = $args[0]
#$function
$powershell = [powershell]::Create().AddScript($function).AddArgument($computer.name).AddArgument($cred).AddArgument($grpName).AddArgument($hash)
$powershell.RunspacePool = $global:runspacepool
#$hash = #{Name=$computer.name;DownProcess = "Waiting.."}
$temp = "" | Select-Object PowerShell,Runspace,Computer
$Temp.Computer = $Computer
$temp.PowerShell = $powershell
$temp.Runspace = $powershell.BeginInvoke()
Write-Verbose ("Adding {0} collection" -f $temp.Computer)
$runspaces.Add($temp) | Out-Null
}
ForEach ($Computer in $list) {
$domain = $computer.Domain
$grpName = $computer.'Computer_Description'
$cred = $(get-Variable "$Domain" -valueOnly)
#Create the powershell instance and supply the scriptblock with the other parameters
if(!$(Get-Variable "TEST_$domain" -ValueOnly)){
CredCheck $computer.name $cred
}
#SendToRunSpace $scriptBlock $computer $domain $global:hash
SendToRunSpace $ServProSel $computer $cred $grpName $global:hash
}
I am running this in PowerShell ISE so i can edit on the fly and test things. When i run this code i generate the $list and $hash items. Ultimately i would like to grab the value out of the $hash for the server and update the corresponding server information in the $list object.
or is there a better way to do this? is the Hashtable the only way to Synchronize data from the runspaces to the current process?
Related
I am trying to add data to an csv file.
I am creating the csv with header first and then trying to add the rows. but it is returning blank csv file
$props=[ordered]#{
ServerName=''
SystemFolderPath=''
IdenityReference=''
FileSystemRights=''
}
New-Object PsObject -Property $props |
Export-Csv "C:\status_report.csv" -NoTypeInformation
$serverlist = Get-Content -Path "C:\ServerList.txt"
foreach($server in $serverlist)
{
$paths_list = $env:Path -Split ';'
Foreach ($sys_Path in $paths_list)
{
$Permissions = Get-Acl -Path $sys_Path
$Users_Permissions = $Permissions.Access | Where-Object {$_.IdentityReference}
#$Users_Permission
Foreach ($user in $Users_Permissions)
{
$IdenityReference = $user.IdentityReference.Value
$FileSystemRights = $user.FileSystemRights
$NewLine = "{0},{1},{2},{3}" -f $server,$sys_Path,$IdenityReference,$FileSystemRights
$NewLine | Export-Csv -Path "C:\status_report.csv" -Append -NoTypeInformation -Force
}
}
}
Please let me know what I am doing wrong here
The main reason why you're seeing this is because Export-Csv expects an object or object[] through the pipeline and you're passing a formatted string instead. This is specified on MS Docs:
Do not format objects before sending them to the Export-CSV cmdlet. If Export-CSV receives formatted objects the CSV file contains the format properties rather than the object properties.
PS /> 'server01,C:\Windows,Computer\User,FullControl' | ConvertTo-Csv
"Length"
"45"
Instead of appending to a CSV which is quite inefficient, unless there is a specific need for this, what you will want to do is collect the results first and then export them.
I'm not too sure why | Where-Object { $_.IdentityReference } is needed, I left it there but I don't think it's needed.
Regarding $serverlist, if you will run this on remote hosts you would be better of using Invoke-Command since it allows parallel invocations. The outer loop wouldn't be needed in that case:
$serverlist = Get-Content -Path "C:\ServerList.txt"
# Collect results here
$result = Invoke-Command -ComputerName $serverlist -ScriptBlock {
$paths_list = $env:Path -Split [System.IO.Path]::PathSeparator
foreach($sys_Path in $paths_list)
{
$Permissions = (Get-Acl -Path $sys_Path).Access
foreach($acl in $Permissions)
{
if(-not $acl.IdentityReference)
{
continue
}
[pscustomobject]#{
ComputerName = $env:ComputerName
SystemFolderPath = $sys_Path
IdenityReference = $acl.IdentityReference.Value
FileSystemRights = $acl.FileSystemRights
}
}
}
} -HideComputerName
$result | Export-Csv -Path "C:\status_report.csv" -NoTypeInformation
Accept Santiago above but this is what I did with what you wrote.
$props = [ordered]#{
ServerName = ''
SystemFolderPath = ''
IdenityReference = ''
FileSystemRights = ''
}
New-Object PsObject -Property $props |
Export-Csv "C:\status_report.csv" -NoTypeInformation
$serverlist = Get-Content -Path "C:\ServerList.txt"
$result = $serverlist | ForEach-Object {
foreach ($server in $_) {
$paths_list = $null
$paths_list = $env:Path -Split ';'
Foreach ($sys_Path in $paths_list) {
$Permissions = Get-Acl -Path $sys_Path
$Users_Permissions = $Permissions.Access | Where-Object { $_.IdentityReference }
#$Users_Permission
Foreach ($user in $Users_Permissions) {
$IdenityReference = $null
$FileSystemRights = $null
$IdenityReference = $user.IdentityReference.Value
$FileSystemRights = $user.FileSystemRights
[PSCustomObject]#{
Server = $server
Sys_Path = $sys_Path
Referecent = $IdenityReference
Rights = $FileSystemRights
}
$sys_Path = $null
}
}
}
}
$result | Export-Csv -Path "C:\status_report.csv" -NoTypeInformation
Santiago's answer is correct and contains all the required information for you to understand the issue you have here.
I just wanted to provide you with the minimum modifications to be done in your script:
Replace the $props custom object by a function (i.e CreateCustomObject)
function CreateCustomObject($val1, $val2, $val3, $val4) {
$NewObject = New-Object PSObject ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "ServerName" -Value $val1 ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "SystemFolderPath" -Value $val2 ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "IdenityReference" -Value $val3 ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "FileSystemRights" -Value $val4 ;
return $NewObject ;
}
Replace the String Variable $NewLine by an Array
$NewLine = #()
$NewLine += CreateCustomObject $server $sys_Path $IdenityReference $FileSystemRights
Write to CSV only once data is collected (move the command to the end of the script)
So the final script will look something like that:
function CreateCustomObject($val1, $val2, $val3, $val4) {
$NewObject = New-Object PSObject ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "ServerName" -Value $val1 ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "SystemFolderPath" -Value $val2 ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "IdenityReference" -Value $val3 ;
Add-Member -InputObject $NewObject -MemberType NoteProperty -Name "FileSystemRights" -Value $val4 ;
return $NewObject ;
}
$serverlist = Get-Content -Path "C:\Temp\ServerList.txt"
$NewLine = #()
foreach($server in $serverlist) {
$paths_list = $env:Path -Split ';'
Foreach ($sys_Path in $paths_list) {
$Permissions = Get-Acl -Path $sys_Path
$Users_Permissions = $Permissions.Access | Where-Object {$_.IdentityReference}
#$Users_Permission
Foreach ($user in $Users_Permissions) {
$IdenityReference = $user.IdentityReference.Value
$FileSystemRights = $user.FileSystemRights
$NewLine += CreateCustomObject $server $sys_Path $IdenityReference $FileSystemRights
}
}
}
$NewLine | Export-Csv -Path "C:\temp\status_report.csv" -NoTypeInformation -Force
I create variables in a ForEach loop using data collected from a CSV file like this:
New-Variable -Name $FlexVPN.'IP-adress' -Value (New-Object PSObject -Property #{
IP = $FlexVPN.'IP-adress'
Information = $FlexVPN.'Information'
Priority = $FlexVPN.'Priority'
RegisteredUp = $RegisteredUp
RegisteredDown = $RegisteredDown
ResponseTime = $Result = try{ Test-Connection -ComputerName $FlexVPN.'IP-adress' -Count $FlexVPN.'Priority' -ErrorAction Stop | Select ResponseTime} catch [System.Net.NetworkInformation.PingException] { $_.exception | PingFailed }})
What I'm then trying to do is to change the values of RegisteredUp and RegisteredDown depending of the respond of the ping.
I doesn't understand the New-Member stuff which I have tried but faild using.
Now I tried Set-Variable but I don´t get how to only change a Object within the Variable?
Set-Variable -Name $FlexVPN.'IP-adress' -Value (New-Object PSObject -Property #{RegisteredDown = "TESTING"})
I don´t get any errors neither it´s working.
To explain further.
If no respond on ping set Get-Date in RegisteredDown for that Variable.
If respond on ping ser Get-Date in RegisteredUp for that Variable.
I then use if/else to use the result somehow in the next version ;)
Edit
# Clear variables after loop
Remove-Variable * -force -erroraction silentlycontinue
function PingFailed {
# Add date and time when IP-address first didn't responded
$FlexVPN.RegisteredDown = 'AnotherTest'
# If only error should be printed
if($PrintError -eq 'Yes'){Write-Host -ForegroundColor Red $FlexVPN.'IP-adress' "," $FlexVPN.'Information'}
##########################################################################
####################### NO CHANGES ABOVE THIS LINE #######################
##########################################################################
# Choose between printing output or not for all rows in CSV-file [Yes/No]
$PrintOutput = 'Yes'
# Choose between printing out error or not [Yes/No]
$PrintError = 'No'
##########################################################################
####################### NO CHANGES BELOW THIS LINE #######################
##########################################################################
# Import CSV-file to Powershell to use data in code
$FlexVPNlist = Import-Csv -Path $PSScriptRoot\PingIPEmail.csv -Header 'IP-adress', 'Information', 'Priority' -Delimiter ';' -Encoding UTF7
Foreach($FlexVPN in $FlexVPNlist) {
New-Variable -Name $FlexVPN.'IP-adress' -Value (New-Object PSObject -Property #{
IP = $FlexVPN.'IP-adress'
Information = $FlexVPN.'Information'
Priority = $FlexVPN.'Priority'
RegisteredDown = 'Test'
ResponseTime = $Result = try{ Test-Connection -ComputerName $FlexVPN.'IP-adress' -Count $FlexVPN.'Priority' -ErrorAction Stop | Select ResponseTime} catch [System.Net.NetworkInformation.PingException] { $_.exception | PingFailed }})
if($PrintOutput -eq 'Yes'){
if ($host.name -eq 'Windows PowerShell ISE Host') {if ($Result.ResponseTime -eq $null) { $Host.UI.RawUI.BackgroundColor = ($bckgrnd = 'Red') } else { $psISE.Options.RestoreDefaults() }}
[PSCustomObject]#{
"IP address" = $FlexVPN.'IP-adress'
"Information" = $FlexVPN.'Information'
"Priority" = $FlexVPN.'Priority'
"Response time" = $Result.ResponseTime
"RegisteredDown" = 'Test'
}}
}
}
My Second try above works fine until I catch an exeption during ping and goes to my function PingFailed.
I want to run that function when an IP-address doesn´t respond and add Get-Date to RegisteredDown in those cases.
The error I recieve is:
At C:\Temp\Powershell scripts\PingIPEmail\PingIPEmail.ps1:49 char:13
+ $FlexVPN.RegisteredDown = 'AnotherTest'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting`
Tried the other code
# Importing a csv
$FlexVPNList = Import-Csv -Path 'C:\Temp\Powershell scripts\PingIPEmail\PingIPEmail.csv' -Header 'IP-adress', 'Information', 'Priority' -Delimiter ';' -Encoding UTF7
Foreach($FlexVPN in $FlexVPNlist) {
$FlexVPN.GetType() # Object[]
$FlexVPN[0].GetType() # PSCustomObject
($FlexVPN[0] | gm -MemberType NoteProperty).Count # 3 Noteproperties
$FlexVPN | % {
Add-Member -InputObject $_ -NotePropertyName 'RegisteredUp' -NotePropertyValue 1 -Force
Add-Member -InputObject $_ -NotePropertyName 'RegisteredDown' -NotePropertyValue 1 -Force
}
($FlexVPN[0] | gm -MemberType NoteProperty).Count # 5 Noteproperties
$Result = try{ Test-Connection -ComputerName $FlexVPN.'IP-adress' -Count $FlexVPN.'Priority' -ErrorAction Stop | Select ResponseTime} catch [System.Net.NetworkInformation.PingException] { $_ }
if ($Result.ResponseTime -eq $null){
if ($host.name -eq 'Windows PowerShell ISE Host') { $Host.UI.RawUI.BackgroundColor = ($bckgrnd = 'Red') }
$FlexVPN.RegisteredDown = Get-Date
[PSCustomObject]#{
"IP address" = $FlexVPN.'IP-adress'
"Information" = $FlexVPN.'Information'
"Priority" = $FlexVPN.'Priority'
"Response time" = $Result.ResponseTime
"RegisteredUp" = $FlexVPN.RegisteredUp
"RegisteredDown" = $FlexVPN.RegisteredDown
}
}
if ($Result.ResponseTime -ge '0'){
if ($host.name -eq 'Windows PowerShell ISE Host') { $psISE.Options.RestoreDefaults() }
$FlexVPN.RegisteredUp = Get-Date
[PSCustomObject]#{
"IP address" = $FlexVPN.'IP-adress'
"Information" = $FlexVPN.'Information'
"Priority" = $FlexVPN.'Priority'
"Response time" = $Result.ResponseTime
"RegisteredUp" = $FlexVPN.RegisteredUp
"RegisteredDown" = $FlexVPN.RegisteredDown
}
}
This code if I understand correctly reuse the same variable for each row in my CSV file.
I want to create one variable for each row (name them the IP-address) so that I can reuse the values stored for as long as the script i running.
Looks like you are overcomplicating things. You can create a new variable (object) like this
$FlexVPN = [PSCustomObject] #{
Information='Test'
}
Show the value of Information
$FlexVPN.Information
Change the value of Information
$FlexVPN.Information = 'AnotherTest'
Show the changed value of Information
$FlexVPN.Information
a valid use case for using new-variable would be if you dynamically create/use variables
Edit
your intent is not actual clear to me but following testbed might get you some new ideas to proceed from
# Mimick importing a csv
$FlexVPN = #'
IP-Adress,Information,Priority
1.1.1.1,FlexVPN,1
2.2.2.2,FlexVPN,2
'# | ConvertFrom-Csv
$FlexVPN.GetType() # Object[]
$FlexVPN[0].GetType() # PSCustomObject
($FlexVPN[0] | gm -MemberType NoteProperty).Count # 3 Noteproperties
$FlexVPN | % {
Add-Member -InputObject $_ -NotePropertyName 'RegisteredUp' -NotePropertyValue 1 -Force
Add-Member -InputObject $_ -NotePropertyName 'RegisteredDown' -NotePropertyValue 1 -Force
}
($FlexVPN[0] | gm -MemberType NoteProperty).Count # 5 Noteproperties
Managed what I wanted by doing like this:
# Clear variables after loop
Remove-Variable * -force -erroraction silentlycontinue
# Importing a csv
$FlexVPNList = Import-Csv -Path 'C:\Temp\Powershell scripts\PingIPEmail\PingIPEmail.csv' -Header 'IP', 'Information', 'Priority' -Delimiter ';' -Encoding UTF7
$FlexVPNList | % {
Add-Member -InputObject $_ -NotePropertyName 'RegisteredUp' -NotePropertyValue '' -Force
Add-Member -InputObject $_ -NotePropertyName 'RegisteredDown' -NotePropertyValue '' -Force
Add-Member -InputObject $_ -NotePropertyName 'ResponseTime' -NotePropertyValue '' -Force
}
Foreach($FlexVPN in $FlexVPNlist) {
$Ping = try{ Test-Connection -ComputerName $FlexVPN.IP -Count $FlexVPN.'Priority' -ErrorAction Stop | Select ResponseTime } catch [System.Net.NetworkInformation.PingException] { $_ }
if($Ping.ResponseTime -ge '0'){
$FlexVPN.RegisteredUp = Get-Date
$FlexVPN.ResponseTime = $Ping.ResponseTime
}
if($Ping.ResponseTime -eq $null){ $FlexVPN.RegisteredDown = Get-Date }
New-Variable -Name $FlexVPN.IP -Value (New-Object PSObject -Property #{
IP = $FlexVPN.IP
Information = $FlexVPN.Information
Priority = $FlexVPN.Priority
RegisteredUp = $FlexVPN.RegisteredUp
RegisteredDown = $FlexVPN.RegisteredDown
ResponseTime = $Ping.ResponseTime
})
[PSCustomObject]#{
"IP address" = $FlexVPN.IP
"Information" = $FlexVPN.Information
"Priority" = $FlexVPN.Priority
"Response time" = $FlexVPN.ResponseTime
"RegisteredUp" = $FlexVPN.RegisteredUp
"RegisteredDown" = $FlexVPN.RegisteredDown
}
}
I can now do stuff If computer responded or not!
I am trying to assign a local variable from within a scriptblock with no luck. The goal is log a status of each machine for the action taken with the data prior and the data after the change. I am not sure how to assign a local variable from within a script block. Any help is much appreciated.
$csvContents = #() # Create the empty array that will eventually be the CSV file
$Computers = Get-ADComputer -Filter '(OperatingSystem -like "Windows Server*") -and (Name -like "AD*")' | Sort-Object Name
foreach ($Computer in $Computers) {
$row = New-Object PSObject # Create an object to append to the array
$row | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value NotSet
$row | Add-Member -MemberType NoteProperty -Name "PingStatus" -Value NotSet
$row | Add-Member -MemberType NoteProperty -Name "DNSChangeStatus" -Value NotSet
$row | Add-Member -MemberType NoteProperty -Name "BeforeChange" -Value NotSet
$row | Add-Member -MemberType NoteProperty -Name "AfterChange" -Value NotSet
#Write-Host "$($Computer.Name): " -ForegroundColor Yellow
$row.ComputerName = $Computer.Name
$rtn = Test-Connection -CN $Computer.dnshostname -Count 1 -BufferSize 16 -Quiet
if ($rtn -match 'True') {
Write-Host -ForegroundColor Green $Computer.DnsHostname
$row.PingStatus = 'Pingable'
Invoke-Command -ComputerName $Computer.Name -ScriptBlock {
$NewDnsServerSearchOrder = "10.93.108.225","10.93.108.134"
$Adapters = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DHCPEnabled -ne 'True' -and $_.DNSServerSearchOrder -eq "10.93.108.226"}
if ($Adapters -ne $null) {
# Show DNS servers before update
Write-Host "Before: " -ForegroundColor Green
$row.DNSChangeStatus = 'Change Needed'
$Adapters | ForEach-Object {
$_.DNSServerSearchOrder
$row.BeforeChange = $_.DNSServerSearchOrder
}
# Update DNS servers
$Adapters | ForEach-Object {$_.SetDNSServerSearchOrder($NewDnsServerSearchOrder)} | Out-Null
# Show DNS servers after update
$Adapters = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DHCPEnabled -ne 'True' -and $_.DNSServerSearchOrder -ne $null}
Write-Host "After: " -ForegroundColor Green
$Adapters | ForEach-Object {
$_.DNSServerSearchOrder
$row.AfterChange = $_.DNSServerSearchOrder
}
} else {
Write-Host "No DNS change needed " -ForegroundColor Yellow
$row.DNSChangeStatus = 'No DNS Change Needed'
}
}
} else {
Write-Host -ForegroundColor Red $Computer.DnsGostname
Write-Host -ForegroundColor Red "Host not pingable"
$row.PingStatus = 'Not Pingable'
}
$csvContents += $row # append the new data to the array
$row = $null
}
$csvContents | Export-Csv -Path C:\DNSChanges.csv
I can't tell exactly what you want to do so I'll guess it's "pass a variable to scriptblock being invoked on a remote machine". If this is the case, you can either add parameters to the scriptblock or use the $using: qualifier as in:
$using:row.DNSChangeStatus = 'Change Needed'
Note that you can't "return" anything that way so you'd have to actually return the modified object from Invoke-Command.
$row = Invoke-Command -ComputerName $Computer.Name -ScriptBlock {
and add
$row
as the last line of the scriptblock.
I am working on a PowerShell script for a customer to pull information from Servers before they are decommissioned. I have had to jump through a couple of hoops, as they have 2008 and even some 2003 servers, which don't always support the same Get-CimInstance queries, but for the most part it is working as expected. One section I have as yet been unable to fix however is pulling a list of all local groups and their members, like so:
$localGroups = Invoke-Command -ScriptBlock {
[ADSI]$S = "WinNT://$($env:computername)"
$S.children.Where({$_.class -eq 'group'}) |
Select #{Name="Name";Expression={$_.name.value}},
#{Name="Members";Expression={
[ADSI]$group = "$($_.Parent)/$($_.Name),group"
$members = $Group.psbase.Invoke("Members")
($members | ForEach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ";"
}
}
} -ComputerName $sPC | Select PSComputername,Name,Members
This works just fine on Server 2012 and above, but fails on 2008 and below. The specific error states:
Method invocation failed because [System.DirectoryServices.DirectoryEntries] doesn't contain a method named 'Where'
So the OS does not like the $S.Children.Where() call. Just curious if anyone knows of another way to format it for use with 2008 and earlier. I could probably go the old WMI call route, but that takes forever to finish.
I ended up finding a (fairly) quick way of doing it, works on all OS flavors:
function get-groups {
param ($strcomputer)
$groups = gwmi win32_group -ComputerName $strcomputer
return $groups
}
function Get-LocalGroupMembers {
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("Name")]
[string]$ComputerName,
[string]$GroupName = "Administrators"
)
begin {}
process {
$ComputerName = $ComputerName.Replace("`$", '')
$arr = #()
$wmi = Get-WmiObject -ComputerName $ComputerName -Query "SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$ComputerName',Name='$GroupName'`""
if ($wmi -ne $null) {
foreach ($item in $wmi) {
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
$hash = #{ComputerName=$ComputerName;Members=$arr}
return $hash
}
end{}
}
$sPC = "Server2008R2"
$a = 1
$adapterInfo = Get-CimInstance -query "Select * From
Win32_NetworkAdapterConfiguration Where IPenabled = 'TRUE'" -ComputerName $sPC
$serialNumber = Get-CimInstance -query "Select * From Win32_Bios" -ComputerName $sPC
$shares = Get-CimInstance -query "Select * From Win32_Share" -ComputerName $sPC
$localGroups = get-groups $sPC
$myObject = New-Object -TypeName PSObject
$myObject | Add-Member -MemberType NoteProperty -Name ComputerName -Value $adapterInfo.PSComputerName
$myObject | Add-Member -MemberType NoteProperty -Name Description -Value $adapterInfo.Description
$adapterInfo | ForEach-Object {
$_.IPAddress |
ForEach-Object {
$myObject | Add-Member -MemberType NoteProperty -Name "IPAddress$($a)" -Value $_
$a++
}
}
$myObject | Add-Member -MemberType NoteProperty -Name SerialNumber -Value $serialNumber.SerialNumber
foreach ($share in $shares) {
if ($share.Name -ne "C$" -and
$share.Name -ne "D$" -and
$share.Name -ne "ADMIN$" -and
$share.Name -ne "IPC$") {
$myObject | Add-Member -MemberType NoteProperty -Name "Share $($share.Name)" -Value $share.Path
}
}
foreach ($group in $localGroups) {
$members = ((Get-LocalGroupMembers -ComputerName $sPC -GroupName $group.Name).Members) -join "; "
if ($members.Length -ne 0) {
$myObject | Add-Member -MemberType NoteProperty -Name "$($group.Name) Members" -Value $members
}
}
$myObject | Export-Csv -Path "<Path to Folder>\$($sPC).csv" -NoTypeInformation
I am searching for a simple command to see logged on users on server.
I know this one :
Get-WmiObject -Class win32_computersystem
but this will not provide me the info I need.
It returns :
domain
Manufactureer
Model
Name (Machine name)
PrimaryOwnerName
TotalPhysicalMemory
I run Powershell 3.0 on a Windows 2012 server.
Also
Get-WmiObject Win32_LoggedOnUser -ComputerName $Computer | Select Antecedent -Unique
gives me not the exact answers I need.
I would love to see as well the idle time, or if they are active or away.
In search of this same solution, I found what I needed under a different question in stackoverflow:
Powershell-log-off-remote-session. The below one line will return a list of logged on users.
query user /server:$SERVER
Since we're in the PowerShell area, it's extra useful if we can return a proper PowerShell object ...
I personally like this method of parsing, for the terseness:
((quser) -replace '^>', '') -replace '\s{2,}', ',' | ConvertFrom-Csv
Note: this doesn't account for disconnected ("disc") users, but works well if you just want to get a quick list of users and don't care about the rest of the information. I just wanted a list and didn't care if they were currently disconnected.
If you do care about the rest of the data it's just a little more complex:
(((quser) -replace '^>', '') -replace '\s{2,}', ',').Trim() | ForEach-Object {
if ($_.Split(',').Count -eq 5) {
Write-Output ($_ -replace '(^[^,]+)', '$1,')
} else {
Write-Output $_
}
} | ConvertFrom-Csv
I take it a step farther and give you a very clean object on my blog.
I ended up making this into a module.
There's no "simple command" to do that. You can write a function, or take your choice of several that are available online in various code repositories. I use this:
function get-loggedonuser ($computername){
#mjolinor 3/17/10
$regexa = '.+Domain="(.+)",Name="(.+)"$'
$regexd = '.+LogonId="(\d+)"$'
$logontype = #{
"0"="Local System"
"2"="Interactive" #(Local logon)
"3"="Network" # (Remote logon)
"4"="Batch" # (Scheduled task)
"5"="Service" # (Service account logon)
"7"="Unlock" #(Screen saver)
"8"="NetworkCleartext" # (Cleartext network logon)
"9"="NewCredentials" #(RunAs using alternate credentials)
"10"="RemoteInteractive" #(RDP\TS\RemoteAssistance)
"11"="CachedInteractive" #(Local w\cached credentials)
}
$logon_sessions = #(gwmi win32_logonsession -ComputerName $computername)
$logon_users = #(gwmi win32_loggedonuser -ComputerName $computername)
$session_user = #{}
$logon_users |% {
$_.antecedent -match $regexa > $nul
$username = $matches[1] + "\" + $matches[2]
$_.dependent -match $regexd > $nul
$session = $matches[1]
$session_user[$session] += $username
}
$logon_sessions |%{
$starttime = [management.managementdatetimeconverter]::todatetime($_.starttime)
$loggedonuser = New-Object -TypeName psobject
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid
$loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage
$loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime
$loggedonuser
}
}
Maybe you can do something with
get-process -includeusername
If you want to find interactively logged on users, I found a great tip here :https://p0w3rsh3ll.wordpress.com/2012/02/03/get-logged-on-users/ (Win32_ComputerSystem did not help me)
$explorerprocesses = #(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
If ($explorerprocesses.Count -eq 0)
{
"No explorer process found / Nobody interactively logged on"
}
Else
{
ForEach ($i in $explorerprocesses)
{
$Username = $i.GetOwner().User
$Domain = $i.GetOwner().Domain
Write-Host "$Domain\$Username logged on since: $($i.ConvertToDateTime($i.CreationDate))"
}
}
Here is my Approach based on DarKalimHero's Suggestion by selecting only on Explorer.exe processes
Function Get-RdpSessions
{
param(
[string]$computername
)
$processinfo = Get-WmiObject -Query "select * from win32_process where name='explorer.exe'" -ComputerName $computername
$processinfo | ForEach-Object { $_.GetOwner().User } | Sort-Object -Unique | ForEach-Object { New-Object psobject -Property #{Computer=$computername;LoggedOn=$_} } | Select-Object Computer,LoggedOn
}
Another solution, also based on query user, but can handle variations in culture (as far as I can tell) and produces strongly-typed results (i.e. TimeSpan and DateTime values):
# Invoke "query user", it produces an output similar to this, but might be culture-dependant!
#
# USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
# >jantje rdp-tcp#55 2 Active . 3/29/2021 4:24 PM
# pietje 4 Disc 49+01:01 4/14/2021 9:26 AM
$result = (&query 'user' | Out-String -Stream)
# Take the header text and insert a '|' before the start of every HEADER - although defined as inserting a bar after
# every 2 or more spaces, or after the space at the start.
$fencedHeader = $result[0] -replace '(^\s|\s{2,})', '$1|'
# Now get the positions of all bars.
$fenceIndexes = ($fencedHeader | Select-String '\|' -AllMatches).Matches.Index
$timeSpanFormats = [string[]]#("d\+hh\:mm", "h\:mm", "m")
$entries = foreach($line in $result | Select-Object -Skip 1)
{
# Insert bars on the same positions, and then split the line into separate parts using these bars.
$fenceIndexes | ForEach-Object { $line = $line.Insert($_, "|") }
$parts = $line -split '\|' | ForEach-Object { $_.Trim() }
# Parse each part as a strongly typed value, using the UI Culture if needed.
[PSCustomObject] #{
IsCurrent = ($parts[0] -eq '>');
Username = $parts[1];
SessionName = $parts[2];
Id = [int]($parts[3]);
State = $parts[4];
IdleTime = $(if($parts[5] -ne '.') { [TimeSpan]::ParseExact($parts[5], $timeSpanFormats, [CultureInfo]::CurrentUICulture) } else { [TimeSpan]::Zero });
LogonTime = [DateTime]::ParseExact($parts[6], "g", [CultureInfo]::CurrentUICulture);
}
}
# Yields the following result:
#
# IsCurrent Username SessionName Id State IdleTime LogonTime
# --------- -------- ----------- -- ----- -------- ---------
# True jantje rdp-tcp#32 2 Active 00:00:00 3/29/2021 4:24:00 PM
# False pietje 4 Disc 48.11:06:00 4/14/2021 9:26:00 AM
$entries | Format-Table -AutoSize
Team!
I have pretty nice solution to get local session as [PSObject].
Function Get-LocalSession {
<#
.DESCRIPTION
Get local session. Pasre output of command - 'query session'.
#>
[OutputType([PSObject[]])]
[CmdletBinding()]
Param(
)
try {
#region functions
#endregion
$Result = #()
$Output = . query.exe 'session' | select-object -skip 1
#use regex to parse
$pattern = '^(?<This>.)(?<SessionName>[^\s]*)\s*(?<UserName>[a-z]\w*)?\s*(?<Id>[0-9]*)\s*(?<State>\w*)\s*((?<Type>\w*)\s*)?(?<Device>\w*)?'
foreach ( $line in $output ){
$match = [regex]::Matches( $line, $pattern )
if ( $match ){
$PSO = [PSCustomObject]#{
This = $match[0].groups['This'].Value
SessionName = $match[0].groups['SessionName'].Value
UserName = $match[0].groups['UserName'].Value
Id = $match[0].groups['Id'].Value
State = $match[0].groups['State'].Value
Type = $match[0].groups['Type'].Value
Device = $match[0].groups['Device'].Value
}
$Result += $PSO
}
Else {
write-host "Unable to process line [$line] in function [Get-LocalSession]!"
}
}
}
catch {
#Get-ErrorReporting -Trap $PSItem
write-host $PSItem
}
return $Result
}
#Run it
$SessionObject = Get-LocalSession
$SessionObject | format-table -autosize -property *
I have edited mjolinor script to remove duplicate records, and dummy account names such as system, network services,...etc
If you want to get all users
function get-loggedonuser ($computername){
$regexa = '.+Domain="(.+)",Name="(.+)"$'
$regexd = '.+LogonId="(\d+)"$'
$logontype = #{
"0"="Local System"
"2"="Interactive" #(Local logon)
"3"="Network" # (Remote logon)
"4"="Batch" # (Scheduled task)
"5"="Service" # (Service account logon)
"7"="Unlock" #(Screen saver)
"8"="NetworkCleartext" # (Cleartext network logon)
"9"="NewCredentials" #(RunAs using alternate credentials)
"10"="RemoteInteractive" #(RDP\TS\RemoteAssistance)
"11"="CachedInteractive" #(Local w\cached credentials)
}
$logon_sessions = #(gwmi win32_logonsession -ComputerName $computername)
$logon_users = #(gwmi win32_loggedonuser -ComputerName $computername)
$session_user = #{}
$logon_users |% {
$_.antecedent -match $regexa > $nul
$username = $matches[1] + "\" + $matches[2]
$_.dependent -match $regexd > $nul
$session = $matches[1]
$session_user[$session] += $username
}
$logon_sessions |%{
$starttime = [management.managementdatetimeconverter]::todatetime($_.starttime)
if ($session_user[$_.logonid] -notin $loggedonuser.user -and $session_user[$_.logonid] -notlike "*$*"){
$loggedonuser = New-Object -TypeName psobject
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid
$loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage
$loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime
$loggedonuser
}
}
}
if you want to have only domain users
function get-loggedonuser ($computername){
$HST= hostname
$regexa = '.+Domain="(.+)",Name="(.+)"$'
$regexd = '.+LogonId="(\d+)"$'
$logontype = #{
"0"="Local System"
"2"="Interactive" #(Local logon)
"3"="Network" # (Remote logon)
"4"="Batch" # (Scheduled task)
"5"="Service" # (Service account logon)
"7"="Unlock" #(Screen saver)
"8"="NetworkCleartext" # (Cleartext network logon)
"9"="NewCredentials" #(RunAs using alternate credentials)
"10"="RemoteInteractive" #(RDP\TS\RemoteAssistance)
"11"="CachedInteractive" #(Local w\cached credentials)
}
$logon_sessions = #(Get-WmiObject win32_logonsession -ComputerName $computername)
$logon_users = #(Get-WmiObject win32_loggedonuser -ComputerName $computername)
$session_user = #{}
$logon_users |ForEach-Object {
$_.antecedent -match $regexa > $nul
$username = $matches[1] + "\" + $matches[2]
$_.dependent -match $regexd > $nul
$session = $matches[1]
$session_user[$session] += $username
}
$logon_sessions |ForEach-Object{
if ($session_user[$_.logonid] -notin $loggedonuser.user -and $session_user[$_.logonid] -notlike "*$*" -and $session_user[$_.logonid] -notlike "*$HST*"){
$loggedonuser = New-Object -TypeName psobject
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid
$loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()]
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage
$loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime
$loggedonuser
}
}
}
This is what I just figured out and works out great!
Get-Process -IncludeUserName | Select-Object -Unique | Where-Object {$_.UserName -notlike 'NT AUTHORITY\SYSTEM' -and $_.UserName -notlike 'NT AUTHORITY\NETWORK SERVICE' -and $_.UserName -notlike 'NT AUTHORITY\LOCAL SERVICE'} | Format-Table -Wrap -AutoSize