Is it possible to create a Custom Object (PSObject) and define its properties beforehand and later in the program execution, we keep adding array of values to the object.
For e.g;
$c = #()
$c = New-Object PSObject
$c | Add-Member -type NoteProperty -name Name
$c | Add-Member -type NoteProperty -name Gender
$c | Add-Member -type NoteProperty -name Age
$c | Add-Member -type NoteProperty -name Name -value "John"
$c | Add-Member -type NoteProperty -name Gender -value "Male"
$c | Add-Member -type NoteProperty -name Age -value "30"
Thanks in advance for any leads or advice.
I'm not sure I follow. Do you want an array of objects with your specified properties? Because your sample first creates an array, that you then overwrite into a single object. So you lost your array.
You can create the object using new-object and specify the properties with values as a hashtable in the -Property parameter. Like this:
$c = New-Object psobject -Property #{
Name = "John"
Gender = "Male"
Age = 30
}
To make an array of them, you can use:
$myarray = #()
$myarray += New-Object psobject -Property #{
Name = "John"
Gender = "Male"
Age = 30
}
If you have multiple tests that you run one by one, you can run the tests in a function that tests and creates a "resultobject", then you collect it:
$myresults = #()
function mytests($computer) {
#Test connection
$online = Test-Connection $computer
#Get buildnumber
$build = (Get-WmiObject win32_operatingsystem -ComputerName $computer).buildnumber
#other tests
#output results
New-Object psobject -Property #{
Online = $online
WinBuild = $build
}
}
$myresults += mytests -computer "mycomputername"
Yeah, so I know this is an old post but Don Jones did something like this:
$props = #{
Name = "John"
Gender = "Male"
Age = 30
}
$c = New-Object PSObject -Property $props
You can run the following to see the Properties and Values the new Object:
c$ | Get-Member
I think that's what you're looking for.
Related
I want to read out PS Versions on the machines in my network with the following funtction:
function CheckPSVersion {
param ([Parameter(Position = 0)][string[]]$computername)
if (!$computername) { $computername = $env:computername }
$y = New-Object PSCustomObject
$computername | % {
$x = Invoke-Command -ComputerName $_ -ScriptBlock { ($PSVersionTable.PSVersion).tostring() }
$y | Add-Member -Membertype NoteProperty -Name Computername -value $_
$y | Add-Member -Membertype NoteProperty -Name PowerShellVersion -Value $x
}
$y
}
But I have problems with Add-Member because it says "an element can't be added to "Computername" because another element with this name already exists"
my desired output would be like this:
Computername PowerShellVersion
------------ -----------------
SIS 5.0.10240.17146
SIS1 2.0.10240.17146
SIS2 5.0.10240.17146
SIS3 3.0.10240.17146
SIS4 5.0.10240.17146
Can you help me? I really can't get my head around add-member
This should suffice:
$computername | ForEach-Object {
New-Object -Type PSCustomObject -Property #{
Computername = $_
PowerShellVersion = Invoke-Command -ComputerName $_ -ScriptBlock {
($PSVersionTable.PSVersion).ToString()
}
}
}
You're trying to add all the computernames to the same object, but actually need a list of objects:
$List = New-Object System.Collections.ArrayList
$computername | % {
$y = New-Object PSCustomObject
$x = Invoke-Command -ComputerName $_ -ScriptBlock {($PSVersionTable.PSVersion).tostring() }
$y | Add-Member -Membertype NoteProperty -Name Computername -value $_
$y | Add-Member -Membertype NoteProperty -Name PowerShellVersion -Value
$List.Add($y)
}
$List
btw, you can replace
param ([Parameter(Position = 0)][string[]]$computername)
if (!$computername) { $computername = $env:computername }
with
param ([Parameter(Position = 0)][string[]]$computername = $env:computername )
I would suggest something like Ansgar's response, I too hate the Add-Member CmdLet, it just looks clunky in code, it might help if do something like this:
$y = #()
1..10 | % {
$y += New-Object PSObject -Property #{ ComputerName = "comp$_"; PowerShellVersion = "1.0" }
}
$y
Essentially you're creating an empty array, ignore the 1.10 loop (I only use it to give the "computers" different names), but as you can see you're adding a PSObject with custom properties to $y, resulting in an array of objects you can manipulate nicely.
I'm trying to get same list of process as in Task Manager in Windows 2008 server
I can't get some values even from WMI objects, such as CPU time, UAC Virtualisation, User Name (e.g. process owner), User Objects, CPU Usage, all memory columns, Handles and threads.
Here is some piece of code I made trying to make it work
Clear-Host
$Processes = Get-Process
foreach ($Process in $Processes) {
$PIDN = $Process.Id
$NAMEProcess = $Process.Name
$NAME = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).Name
$PATH = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).Path
$CMD = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).CommandLine
$OWNER = (Get-WmiObject win32_process | where {$_.Name -match $NAMEProcess}).getowner().user
$SESSIONID = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).SessionId
$CPU = $Process.CPU
$WORKINGSET64 = $Process.WorkingSet64
$PEAKWORKINGSET64 = $Process.PeakWorkingSet64
$THREADS = $Process.Threads.Count
$HANDLES = $Process.Handles
$DESCRIPTION = $Process.Description
$obj = new-object psobject
$obj | add-member noteproperty "PID" ($PIDN)
$obj | add-member noteproperty "NAME" ($NAME)
$obj | add-member noteproperty "OWNER" ($OWNER)
$obj | add-member noteproperty "PATH" ($PATH)
$obj | add-member noteproperty "Command Line" ($CMD)
$obj | Add-Member noteproperty "SessionID" ($SESSIONID)
$obj | Add-Member noteproperty "CPU" ($CPU)
$obj | Add-Member noteproperty "WorkingSet64" ($WORKINGSET64)
$obj | Add-Member noteproperty "Peak Working Set64" ($PEAKWORKINGSET64)
$obj | Add-Member noteproperty "HANDLES" ($HANDLES)
$obj | Add-Member noteproperty "THREADS" ($THREADS)
$obj | Add-Member noteproperty "DESCRIPTION" ($DESCRIPTION)
write-output $obj | Format-Table
# $obj | Format-Table $PIDN, $NAME
}
Also couldn't make it to output in proper table. Could you please help me with that? Thank you.
did you take a look at performance counters?
Get-Counter "\Process(*)\Working Set - Private"
Get-Counter "\Process(*)\Handle Count"
For further documentation on Get-Counter use
get-help get-counter -Full
or goto: https://technet.microsoft.com/en-us/library/hh849685.aspx
First, you need to be using the data from a single use of Get-WMIObject (gwmi).
When you make multiple calls using GWMI, you're effectively taking another snapshot of data every time and using a different sample for each property. You're going to end up with a table of data befitting of a cubist Picasso painting... it won't align or represent the whole list. It also takes a lot more time to recapture the process list over and over then grab only a single property from each different list, so its worth it to take the time to modify a single set of data, especially if you end up repurposing your script in a massive remote operations script that compiles a database of tasks.
There are different ways to grab only the properties you want in the form of PS Custom Objects. I use hash tables to make the code short and easy, and to make the code efficient--
You already executed the verbage you would have done for a hash table, in the form of:
$CPU = $Process.CPU
$WORKINGSET64 = $Process.WorkingSet64
$PEAKWORKINGSET64 = $Process.PeakWorkingSet64
$THREADS = $Process.Threads.Count
$HANDLES = $Process.Handles
$DESCRIPTION = $Process.Description
So instead, just do
$taskProps = #{
'SID'=$task.SessionId
'Name'=$task.ProcessName
'PID'=$task.ProcessId
# add more properties here.
}
And instead of creating a blank custom object, and then 'writing' to it multiple times with
$obj = new-object psobject
$obj | add-member noteproperty "PID" ($PIDN)
$obj | add-member noteproperty "NAME" ($NAME)
$obj | add-member noteproperty "OWNER" ($OWNER)
$obj | add-member noteproperty "PATH" ($PATH)
$obj | add-member noteproperty "Command Line" ($CMD)
$obj | Add-Member noteproperty "SessionID" ($SESSIONID)
$obj | Add-Member noteproperty "CPU" ($CPU)
$obj | Add-Member noteproperty "WorkingSet64" ($WORKINGSET64)
$obj | Add-Member noteproperty "Peak Working Set64" ($PEAKWORKINGSET64)
$obj | Add-Member noteproperty "HANDLES" ($HANDLES)
$obj | Add-Member noteproperty "THREADS" ($THREADS)
$obj | Add-Member noteproperty "DESCRIPTION" ($DESCRIPTION)
You can package the property hashtable and create in a single shot, the custom object at the end:
$taskObject = New-Object -TypeName PSObject -Property $taskProps
Then store it in an arraylist with
$taskList += $taskObject
You can see my example here:
# Generates a collection of "System.Management.ManagementObject#root\cimv2\Win32_Process"
# Only do this once. Every time gwmi is used, it makes another RPC call if used remotely.
# If you do multiple GWMIs you'll be working with differing data samples.
$taskSnapshot = Get-WMIObject -ComputerName [machine name] -Class Win32_Process
# Initialize, nullify, and declare your list as an empty ArrayList.
$taskList = #()
# Begin formatting in prep of Format-Table or similar usage
# This is where you'd define each property you want to see, manipulation, etc.
foreach ($task in $taskSnapshot){
# Create the hash table which will temporarily store all information for each task naming/assigning only
# properties you want to display.
$taskProps = #{
'SID'=$task.SessionId
'Name'=$task.ProcessName
'PID'=$task.ProcessId
# additional properties here.
}
# "Packages" the new custom object in a variable that stores the object
$taskObject = New-Object -TypeName PSObject -Property $taskProps
# append (addition) operation on formerly defined arraylist to store
# the packaged object to an arraylist.
$taskList += $taskObject
}
# Displays the list of task "objects" in a table, other formatting options are available online.
$taskList | Format-Table -AutoSize
Using Format Commands to Change Output View:
https://technet.microsoft.com/en-us/library/dd347677.aspx
Windows PowerShell: The Many Ways to a Custom Object:
https://technet.microsoft.com/en-us/magazine/hh750381.aspx
I would also recommend checking out Out-GridView as it will create a GUI table of the data which you can resize and easily click around in.
The important part is to use GWMI once. It would be better practice to capture the raw information in a single variable, then perform your Select-String/where/if/manipulation and formatting operations on the dataset within your foreach statement.
Here's the clean copy of the example, with my select aliases.
$taskSnapshot = gwmi -cn localhost -class win32_process
$taskList = #()
foreach ($task in $taskSnapshot){
$taskProps = #{
'SID'=$task.SessionId
'Name'=$task.ProcessName
'PID'=$task.ProcessId
}
$taskObject = New-Object -TypeName PSObject -Property $taskProps
$taskList += $taskObject
}
$taskList | Out-GridView
Another thing someone mentioned is the min/max working set properties...
You can look at all the properties of Win32_Process by doing
Get-WMIObject -Class Win32_Process | Get-Member
or
gwmi -cl win32_process | gm
To build on Chris Kuperstein answer:
I do not like that PowerShell rearranges my properties according to the default sort of the Keys collection in the HashTable; I put my properties in a distinct order for a reason. To counteract this you can utilize property sets. Worth noting that you absolutely could use Select-Object to achieve this too.
$taskObject = New-Object -TypeName PSObject -Property $taskProps
$taskObject.PSObject.TypeNames.Insert(0, "xTask")
$taskObject | Add-Member -MemberType ScriptMethod -Name "ToString" -Force -Value {
Write-Output "Name: $($this.Name) [$($this.PID)] - CPU: $($this.CPU)"
}
$defaultDisplaySet = #('SID', 'PID', 'Name', 'CPU', 'Threads', 'Handles', 'WorkingSet64', 'Owner', 'Description', 'Path')
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet("DefaultDisplayPropertySet",[string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]#($defaultDisplayPropertySet)
$taskObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
I have a custom PS Object that is something like the below:
ID Folder
MyServer01 \\Server\Share\Share\MyServer01
MyServer02 \\Server\Share\Share\MyServer02
Naturally the object itself is rather large, with over 1000 entries. I need to be able to select a specific row of the object based on querying the ID.
I thought something like this would work but I'm not having much luck:
$obj | Select-Object | Where-Object ($_.ID -eq "MyServer01")
I need it to return the entire row, so the above (assuming it worked) would return:
MyServer01 \\Server\Share\Share\MyServer01
EDIT:
foreach ($mf in $Folders.Tables[0]) {
$Info = New-Object System.Object
$Info | Add-Member -Type NoteProperty -Name ID -Value $mf.ID
$Info | Add-Member -Type NoteProperty -Name Folder -Value $mf.Folder
$obj += $Info
}
Use a hashtable for storing your objects:
$obj = #{}
foreach ($mf in $Folders.Tables[0]) {
$Info = New-Object -Type System.Object
$Info | Add-Member -Type NoteProperty -Name ID -Value $mf.ID
$Info | Add-Member -Type NoteProperty -Name Folder -Value $mf.Folder
$obj[$mf.ID] = $Info
}
Don't append to an array in a loop, as that tends to perform poorly.
If your code doesn't depend on the objects being created explicitly as System.Object I'd also recommend to create them as custom objects:
$obj = #{}
foreach ($mf in $Folders.Tables[0]) {
$Info = New-Object -Type PSCustomObject -Property #{
'ID' = $mf.ID
'Folder' = $mf.Folder
}
$obj[$mf.ID] = $Info
}
I'm trying trying to get two properties from two separate commands and add them to a variable to be able to further evaluate.
I was told a custom object would work...
Clear-Host
Add-PSSnapin citrix* -ErrorAction SilentlyContinue
$DRSrvs = Get-XAServer drptsw00* | select -ExpandProperty servername
$hash = $null
$hash = #{}
foreach ($DR in $DRSrvs) {
$hash = New-Object PsObject -Property #{
servername = $DR
Logins = (Get-XALoadEvaluator -ServerName $DR).LoadEvaluatorName
}
}
A hashtable is for mapping (unique) keys to values. If you need to map different servernames to login names use a hashtable, otherwise use custom objects. Either way you need to handle the data structures correctly.
Hashtable:
$hash = #{}
foreach ($DR in $DRSrvs) {
$hash[$DR] = (Get-XALoadEvaluator -ServerName $DR).LoadEvaluatorName
}
Custom object list:
$list = foreach ($DR in $DRSrvs) {
New-Object PsObject -Property #{
servername = $DR
Logins = (Get-XALoadEvaluator -ServerName $DR).LoadEvaluatorName
}
}
Assigning something to a variable in a loop replaces the previous value in that variable with each iteration, leaving you with just the last value after the loop finishes.
I used this method and got a very clean output. Citrix SDK for Powershell if very funny and has lots of gotchas.
Clear-Host
Add-PSSnapin citrix* -ErrorAction SilentlyContinue
$OutputData = $null
$OutputData = #()
$Srvs = Get-XAServer Srv123* | Select-Object -ExpandProperty ServerName
$object = New-Object PSObject
Add-Member -InputObject $object -MemberType NoteProperty -Name Servername -Value ""
Add-Member -InputObject $object -MemberType NoteProperty -Name LoadEval -Value ""
foreach ($Srv in $Srvs) {
$servername= $Srv
$LoadEval = ((Get-XALoadEvaluator -ServerName $Srv).LoadEvaluatorName)
$appObject = New-Object System.Object
$appObject |
Add-Member -MemberType NoteProperty -Name "ServerName" -Value $servername -PassThru |
Add-Member -MemberType NoteProperty -Name "LoadEval" -Value $LoadEval
$outputData += $appObject
}
I use Powershell's custom-object command to hold data points. Custom-object creates just one object and assigns a variable to it. Can Powershell go one step further and create new classes from which objects can be made?
In the examples below, I store three pieces of data: a server name, a timestamp, and the minutes since an event occurred on the server.
When I was learning Powershell, I put all this into a two-dimensional array:
$record = #("Server","Timestamp","Minutes")
for ($j = 0; $j -lt 10; $j++){
$record += #("Server1","$(get-date)",$j)
sleep 60
}
$record | export-csv -path c:\record.csv -no type information
export-csv doesn't play well with arrays, so I started using a custom object:
$record = #()
for ($j = 0; $j -lt 10; $j++){
$r = New-Object -TypeName PSObject
$r | Add-Member -MemberType NoteProperty -Name Server -Value ""
$r | Add-Member -MemberType NoteProperty -Name Timesteamp -Value ""
$r | Add-Member -MemberType NoteProperty -Name Minutes -Value ""
$r.server = "Server1"
$r.timestamp = "$(get-date)"
$r.minutes = "$j"
$record += $r
sleep 60
}
$record | export-csv -path c:\record.csv -no type information
That's exports correctly, and dealing with object properties is easier than dealing with columns in a two-dimensional array.
But if I want to create several custom objects that aren't in an array, I have to write the custom-object code over and over again.
$server1 = New-Object -TypeName PSObject
$server1 | Add-Member -MemberType NoteProperty -Name Server -Value ""
$server1 | Add-Member -MemberType NoteProperty -Name Timesteamp -Value ""
$server2 = New-Object -TypeName PSObject
$server2 | Add-Member -MemberType NoteProperty -Name Server -Value ""
$server2 | Add-Member -MemberType NoteProperty -Name Timesteamp -Value ""
#ad nauseum
What if Powershell could design custom classes in addition to custom objects? Like OO programming languages do? Something like:
class record {
-MemberType NoteProperty -Name Server -Value ""
-MemberType NoteProperty -Name Timestamp -Value ""
-MemberType NoteProperty -Name Minutes -Value ""
}
$server1 = new-object -TypeName record
$server2 = new-object -TypeName record
$server3 = new-object -TypeName record
Is that possible in Powershell?
You can define classes in PowerShell.
Add-Type -Language CSharp #"
public class Record{
public System.DateTime TimeStamp;
public string Server;
public int Minutes;
}
"#;
$MyRecord = new-object Record;
$MyRecord.Server = "myserver";
$MyRecord.Timestamp = Get-Date;
$MyRecord.Minutes = 15;
You could use a function as a faux constructor for your custom objects. You wouldn't ever have to duplicate your code, and you could use flags to set your properties right from the function call. Here's an example:
Function New-Constructor
{
param
(
[string]$Name,
[DateTime]$TimeStamp = (Get-Date)
)
$server = New-Object -TypeName PSObject
$server | Add-Member -MemberType NoteProperty -Name Server -Value $Name
$server | Add-Member -MemberType NoteProperty -Name TimeStamp -Value $TimeStamp
# Calling "server" below outputs it, acting as a "return" value
$server
}
And some sample output:
PS C:\> New-Constructor -Name "MyServer"
Server TimeStamp
------ ---------
MyServer 9/9/2013 3:27:47 PM
PS C:\> $myServer = New-Constructor -Name "MyServer"
PS C:\> $myServer
Server TimeStamp
------ ---------
MyServer 9/9/2013 3:27:57 PM
PS C:\> $newServer = New-Constructor -Name "NS" -TimeStamp (Get-Date).AddDays(-1)
PS C:\> $newServer
Server TimeStamp
------ ---------
NS 9/8/2013 3:33:00 PM
You can do a whole ton of stuff with functions that is out of the scope of this question. Instead, check out about_functions_advanced.
Another option.
Properties
You can replace the '$null' value of the property message to have an initial value.
The Prop object is a hashtable of keys (properties) and values (initial values).
$messageClass = New-Object -TypeName PSObject -Prop #{ message = $null; }
Methods
$messageClass | Add-Member -MemberType ScriptMethod -Name "ShowMessage" -Value {
Try
{
Write-Host $this.message
}
Catch
{
Throw $_.Exception
}
}
Constructors
The code below describes a constructor. Polymorphism is achieved using [Parameter(Mandatory=$false)] to assert or not the provision of the specified parameter.
function MessageClass {
param([Parameter(Mandatory=$true)]
[String]$mandatoryMessage,
[Parameter(Mandatory=$false)]
[String]$optionalMessage)
$messageObj = $messageClass.psobject.copy()
if ($optionalMessage)
{
$messageObj.message = "$mandatoryMessage $optionalMessage!"
}
else
{
$messageObj.message = "$mandatoryMessage!"
}
$messageObj
}
The constructor can then be called like this:
$var1 = 'Hello'
$var2 = 'World'
$example1 = MessageClass -mandatoryMessage $var1
$example2 = MessageClass -mandatoryMessage $var1 -optionalMessage $var2
To show the text:
$example1.ShowMessage()
$example2.ShowMessage()
The results would be:
Hello!
Hello World!
For best performance I would do it like that:
Add-Type -TypeDefinition '
public class recordEntry {
public string server;
public System.DateTime timestamp;
public int minutes;
public recordEntry(string _server, System.DateTime _timestamp, int _minutes) {
server = _server;
timestamp = _timestamp;
minutes = _minutes;
}
}'
$record = [System.Collections.ArrayList]#()
$record = foreach ($j in 0..10){
[recordEntry]::new("Server1", [datetime]::Now, $j)
}
$record | export-csv -path c:\record.csv -NoTypeInformation