Cloning Array variables in Powershell - It seems to retain a link [duplicate] - powershell

I've been banging my head against the wall on this one.
I know that if I create an Array in Powershell, then copy the array, it'll copy it as a reference type not a value type.
So, the classic example is:
$c = (0,0,0)
$d = $c
$c[0] = 1
$d
1
0
0
The solution is to do $d = $c.clone()
This isn't working though if the array itself is a collection of reference types. This is my problem. I'm trying to create an array to track CPU usage by creating an array of Processes, wait a while, then check the latest values and calculate the differences. However the Get-Process creates a reference array. So when I do the following:
$a = ps | sort -desc id | where-object {$_.CPU -gt 20} #Get current values
$b = $a.clone() #Create a copy of those values.
sleep 20 #Wait a few seconds for general CPU usage...
$a = ps | sort -desc id | where-object {$_.CPU -gt 20} #Get latest values.
$a[0]
$b[0] #returns the same value as A.
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessNam
------- ------ ----- ----- ----- ------ -- ----------
3195 57 90336 100136 600 83.71 7244 OUTLOOK
$a and $b will always return the same value. If I try and do it one entry at a time using something like $b[0] = "$a[0].clone()" - PS complains that Clone can't be used in this case.
Any suggestions??
Also, just FYI, the second $a = PS |.... line isn't actually needed since $a is reference type to the PS list object, it actually gets updated and returns the most current values whenever $a is called. I included it to make it clearer what I'm trying to accomplish here.

To copy an array, you can do the following:
$c = (0,0,0)
$d = $c | foreach { $_ }
$c[0] = 1
"c is [$c]"
"d is [$d]"
Result
c is [1 0 0]
d is [0 0 0]
For your particular issue (comparing CPU usage of processes), something more specific would probably be better, as Keith pointed out.

Technically $d = $c is not any sort of array copy (of reference or value). It's just stashing a reference to the array $c refers to, into $d. I think you only need to grab the array of processes once and then call the Refresh method. You'll have to check the Exited property first to make sure the associated process is still running. Of course, this won't help you if you're interested in any new process that start up. In that case, grab a snapshot of processes at different times, weed out all but the intersection of processes between the two arrays (by process Id) and then compute the differences in their property values - again based on process Id. Take make this easier, you might want to put each snapshot in a hashtable keyed off the process Id.

Related

Adding Header to the Variable

This is probably a dumb question but I cant seem to figure it out. How do I add a header to already existing variable? I have a variable with bunch of strings in it and I am trying to make it so it has a header which will simplify the script later on. Just as an example
$test = 1,2,3,4,5,6
Which comes out to be:
PS C:\windows\system32> $test
1
2
3
4
5
6
Where as what I want it to do is:
PS C:\windows\system32> $test
Numbers
--------
1
2
3
4
5
6
Additionally when implementing for each loop is it possible to add a blank header like to existing variable (from which foreach loop is running) and fill it automatically? for example going from original variable:
Letters Value
------- -----
a 10
b 15
c 23
d 25
To after for each loop:
Letters Value Numbers
------- ----- ------
a 10 1
b 15 2
c 23 3
d 25 4
This is a super generic example but basically i have one object with headers and when using a function someone made I am trying to populate the table with output of that function, the issue is that its returning stuff with no header and just returns the output only so I cant even make a hash table.
Thanks in advance.
In your example, your variable is a list of integers.
That's why there's no header.
If your variable were something else, like, a custom object, it would be displayed with headers.
To make your example a list of custom objects:
$test = 1..6
$test | Foreach-Object { [PSCustomObject]#{Number=$_} }
You can save this back to a variable:
$test = 1..6
$testObjects = $test | Foreach-Object { [PSCustomObject]#{Number=$_} }
If an object has four or fewer properties, it will be displayed as a table.
So you could also, say, make an object with two properties and still get headers.
$test = 1..6
$test | Foreach-Object { [PSCustomObject]#{Number=$_;NumberTimesTwo = $_ * 2} }
If you want to control how any object displays in PowerShell, you'll want to learn about writing formatters. There's a module I make called EZOut that makes these a lot easier to work with.
To offer an alternative to Start-Automating's helpful answer:
You can use Select-Object with calculated properties:
To turn a list of numbers into objects ([pscustomobject] instances) with a .Number property, whose display formatting defaults to the tabular display you're looking for:
$objects =
1,2,3,4,5,6 | Select-Object #{ Name='Number'; Expression={ $_ } }
Outputting $objects yields:
Number
------
1
2
3
4
5
6
You can use the same technique for adding additional properties to (copies of) existing objects, filling them at the same time (builds on the $objects variable filled above):
# Values for the new property.
$nums = 6..1 # same as: 6, 5, 4, 3, 2, 1
$i = #{ val = 0 } # Helper hashtable for indexing into $nums
$objects | Select-Object *, #{ Name='NewProperty'; Expression={ $nums[$i.val++] } }
Output:
Numbers NewProperty
------- -----------
1 6
2 5
3 4
4 3
5 2
6 1
If you want to add a property with $null values first and fill them later, use:
$objects | Select-Object *, NewProperty

powershell get mac adress and output as text [duplicate]

Let's say we have an array of objects $objects. Let's say these objects have a "Name" property.
This is what I want to do
$results = #()
$objects | %{ $results += $_.Name }
This works, but can it be done in a better way?
If I do something like:
$results = objects | select Name
$results is an array of objects having a Name property. I want $results to contain an array of Names.
Is there a better way?
I think you might be able to use the ExpandProperty parameter of Select-Object.
For example, to get the list of the current directory and just have the Name property displayed, one would do the following:
ls | select -Property Name
This is still returning DirectoryInfo or FileInfo objects. You can always inspect the type coming through the pipeline by piping to Get-Member (alias gm).
ls | select -Property Name | gm
So, to expand the object to be that of the type of property you're looking at, you can do the following:
ls | select -ExpandProperty Name
In your case, you can just do the following to have a variable be an array of strings, where the strings are the Name property:
$objects = ls | select -ExpandProperty Name
As an even easier solution, you could just use:
$results = $objects.Name
Which should fill $results with an array of all the 'Name' property values of the elements in $objects.
To complement the preexisting, helpful answers with guidance of when to use which approach and a performance comparison.
Outside of a pipeline[1], use (requires PSv3+):
$objects.Name # returns .Name property values from all objects in $objects
as demonstrated in rageandqq's answer, which is both syntactically simpler and much faster.
Accessing a property at the collection level to get its elements' values as an array (if there are 2 or more elements) is called member-access enumeration and is a PSv3+ feature.
Alternatively, in PSv2, use the foreach statement, whose output you can also assign directly to a variable: $results = foreach ($obj in $objects) { $obj.Name }
If collecting all output from a (pipeline) command in memory first is feasible, you can also combine pipelines with member-access enumeration; e.g.:
(Get-ChildItem -File | Where-Object Length -lt 1gb).Name
Tradeoffs:
Both the input collection and output array must fit into memory as a whole.
If the input collection is itself the result of a command (pipeline) (e.g., (Get-ChildItem).Name), that command must first run to completion before the resulting array's elements can be accessed.
In a pipeline, in case you must pass the results to another command, notably if the original input doesn't fit into memory as a whole, use: $objects | Select-Object -ExpandProperty Name
The need for -ExpandProperty is explained in Scott Saad's answer (you need it to get only the property value).
You get the usual pipeline benefits of the pipeline's streaming behavior, i.e. one-by-one object processing, which typically produces output right away and keeps memory use constant (unless you ultimately collect the results in memory anyway).
Tradeoff:
Use of the pipeline is comparatively slow.
For small input collections (arrays), you probably won't notice the difference, and, especially on the command line, sometimes being able to type the command easily is more important.
Here is an easy-to-type alternative, which, however is the slowest approach; it uses ForEach-Object via its built-in alias, %, with simplified syntax (again, PSv3+):
; e.g., the following PSv3+ solution is easy to append to an existing command:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
Note: Use of the pipeline is not the primary reason this approach is slow, it is the inefficient implementation of the ForEach-Object (and Where-Object) cmdlets, up to at least PowerShell 7.2. This excellent blog post explains the problem; it led to feature request GitHub issue #10982; the following workaround greatly speeds up the operation (only somewhat slower than a foreach statement, and still faster than .ForEach()):
# Speed-optimized version of the above.
# (Use `&` instead of `.` to run in a child scope)
$objects | . { process { $_.Name } }
The PSv4+ .ForEach() array method, more comprehensively discussed in this article, is yet another, well-performing alternative, but note that it requires collecting all input in memory first, just like member-access enumeration:
# By property name (string):
$objects.ForEach('Name')
# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
This approach is similar to member-access enumeration, with the same tradeoffs, except that pipeline logic is not applied; it is marginally slower than member-access enumeration, though still noticeably faster than the pipeline.
For extracting a single property value by name (string argument), this solution is on par with member-access enumeration (though the latter is syntactically simpler).
The script-block variant ({ ... }) allows arbitrary transformations; it is a faster - all-in-memory-at-once - alternative to the pipeline-based ForEach-Object cmdlet (%).
Note: The .ForEach() array method, like its .Where() sibling (the in-memory equivalent of Where-Object), always returns a collection (an instance of [System.Collections.ObjectModel.Collection[psobject]]), even if only one output object is produced.
By contrast, member-access enumeration, Select-Object, ForEach-Object and Where-Object return a single output object as-is, without wrapping it in a collection (array).
Comparing the performance of the various approaches
Here are sample timings for the various approaches, based on an input collection of 10,000 objects, averaged across 10 runs; the absolute numbers aren't important and vary based on many factors, but it should give you a sense of relative performance (the timings come from a single-core Windows 10 VM:
Important
The relative performance varies based on whether the input objects are instances of regular .NET Types (e.g., as output by Get-ChildItem) or [pscustomobject] instances (e.g., as output by Convert-FromCsv).
The reason is that [pscustomobject] properties are dynamically managed by PowerShell, and it can access them more quickly than the regular properties of a (statically defined) regular .NET type. Both scenarios are covered below.
The tests use already-in-memory-in-full collections as input, so as to focus on the pure property extraction performance. With a streaming cmdlet / function call as the input, performance differences will generally be much less pronounced, as the time spent inside that call may account for the majority of the time spent.
For brevity, alias % is used for the ForEach-Object cmdlet.
General conclusions, applicable to both regular .NET type and [pscustomobject] input:
The member-enumeration ($collection.Name) and foreach ($obj in $collection) solutions are by far the fastest, by a factor of 10 or more faster than the fastest pipeline-based solution.
Surprisingly, % Name performs much worse than % { $_.Name } - see this GitHub issue.
PowerShell Core consistently outperforms Windows Powershell here.
Timings with regular .NET types:
PowerShell Core v7.0.0-preview.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.005
1.06 foreach($o in $objects) { $o.Name } 0.005
6.25 $objects.ForEach('Name') 0.028
10.22 $objects.ForEach({ $_.Name }) 0.046
17.52 $objects | % { $_.Name } 0.079
30.97 $objects | Select-Object -ExpandProperty Name 0.140
32.76 $objects | % Name 0.148
Windows PowerShell v5.1.18362.145
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.012
1.32 foreach($o in $objects) { $o.Name } 0.015
9.07 $objects.ForEach({ $_.Name }) 0.105
10.30 $objects.ForEach('Name') 0.119
12.70 $objects | % { $_.Name } 0.147
27.04 $objects | % Name 0.312
29.70 $objects | Select-Object -ExpandProperty Name 0.343
Conclusions:
In PowerShell Core, .ForEach('Name') clearly outperforms .ForEach({ $_.Name }). In Windows PowerShell, curiously, the latter is faster, albeit only marginally so.
Timings with [pscustomobject] instances:
PowerShell Core v7.0.0-preview.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.006
1.11 foreach($o in $objects) { $o.Name } 0.007
1.52 $objects.ForEach('Name') 0.009
6.11 $objects.ForEach({ $_.Name }) 0.038
9.47 $objects | Select-Object -ExpandProperty Name 0.058
10.29 $objects | % { $_.Name } 0.063
29.77 $objects | % Name 0.184
Windows PowerShell v5.1.18362.145
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.008
1.14 foreach($o in $objects) { $o.Name } 0.009
1.76 $objects.ForEach('Name') 0.015
10.36 $objects | Select-Object -ExpandProperty Name 0.085
11.18 $objects.ForEach({ $_.Name }) 0.092
16.79 $objects | % { $_.Name } 0.138
61.14 $objects | % Name 0.503
Conclusions:
Note how with [pscustomobject] input .ForEach('Name') by far outperforms the script-block based variant, .ForEach({ $_.Name }).
Similarly, [pscustomobject] input makes the pipeline-based Select-Object -ExpandProperty Name faster, in Windows PowerShell virtually on par with .ForEach({ $_.Name }), but in PowerShell Core still about 50% slower.
In short: With the odd exception of % Name, with [pscustomobject] the string-based methods of referencing the properties outperform the scriptblock-based ones.
Source code for the tests:
Note:
Download function Time-Command from this Gist to run these tests.
Assuming you have looked at the linked code to ensure that it is safe (which I can personally assure you of, but you should always check), you can install it directly as follows:
irm https://gist.github.com/mklement0/9e1f13978620b09ab2d15da5535d1b27/raw/Time-Command.ps1 | iex
Set $useCustomObjectInput to $true to measure with [pscustomobject] instances instead.
$count = 1e4 # max. input object count == 10,000
$runs = 10 # number of runs to average
# Note: Using [pscustomobject] instances rather than instances of
# regular .NET types changes the performance characteristics.
# Set this to $true to test with [pscustomobject] instances below.
$useCustomObjectInput = $false
# Create sample input objects.
if ($useCustomObjectInput) {
# Use [pscustomobject] instances.
$objects = 1..$count | % { [pscustomobject] #{ Name = "$foobar_$_"; Other1 = 1; Other2 = 2; Other3 = 3; Other4 = 4 } }
} else {
# Use instances of a regular .NET type.
# Note: The actual count of files and folders in your file-system
# may be less than $count
$objects = Get-ChildItem / -Recurse -ErrorAction Ignore | Select-Object -First $count
}
Write-Host "Comparing property-value extraction methods with $($objects.Count) input objects, averaged over $runs runs..."
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Name },
{ $objects | % Name },
{ $objects | % { $_.Name } },
{ $objects.ForEach('Name') },
{ $objects.ForEach({ $_.Name }) },
{ $objects.Name },
{ foreach($o in $objects) { $o.Name } }
# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Factor, Command, Secs*
[1] Technically, even a command without |, the pipeline operator, uses a pipeline behind the scenes, but for the purpose of this discussion using the pipeline refers only to commands that use |, the pipeline operator, and therefore by definition involve multiple commands.
Caution, member enumeration only works if the collection itself has no member of the same name. So if you had an array of FileInfo objects, you couldn't get an array of file lengths by using
$files.length # evaluates to array length
And before you say "well obviously", consider this. If you had an array of objects with a capacity property then
$objarr.capacity
would work fine UNLESS $objarr were actually not an [Array] but, for example, an [ArrayList]. So before using member enumeration you might have to look inside the black box containing your collection.
(Note to moderators: this should be a comment on rageandqq's answer but I don't yet have enough reputation.)
I learn something new every day! Thank you for this. I was trying to achieve the same. I was directly doing this:
$ListOfGGUIDs = $objects.{Object GUID}
Which basically made my variable an object again! I later realized I needed to define it first as an empty array,
$ListOfGGUIDs = #()

Powershell Calculated properties and Tee-object/Variables

I'm trying to write a few scripts using calculated properties (love that feature), and I seem to have an issue when I assign variables in it, for example:
ls | select #{N='What';E={get-acl | Tee-Object -Variable something}},#{N="ok";E={$something}}
it seems that the output I'm getting is:
And I'm unsure if there's a way to save a variable so I can use it in a different calculated property or even just piped to the next command? ( I do know that you can use that variable in the same calculated property though.)
Thanks to anyone who helps.
If you need to construct many inter-related properties and Select-Object is too verbose or inefficient, the idiomatic approach is to pipe to ForEach-Object and construct the output object manually:
Get-ChildItem |ForEach-Object {
$acl = $_ |Get-Acl
# now we can just use this local variable directly
$isOK = Test-WhateverYouNeedToTest $acl
# construct new object
[pscustomobject]#{
FilePath = $_.FullName
ACL = $acl
RuleCount = $acl.Access.Count
IsOK = $isOK
}
}
I'm unsure if there's a way to save a variable so I can use it in a different calculated property
You can't "cross-reference" the resolved value of one calculated property expression from another in the same call, if that's what you mean.
Property expressions are executed in their own local scope, which is why variable assignments also don't persist.
You could (but probably shouldn't) assign to a variable in a parent scope:
ls | select Name,#{N='ACL';E={ ($global:something = $_ |Get-Acl) }},#{N='RuleCount';E={$something.Access.Count}}
I would strongly recommend against it - keeping calculated properties free of side effects will make your code easier to read and understand.
[...] or even just piped to the next command
Again, you could, (but probably shouldn't) assign to a variable in a parent scope and use that with the downstream command:
ls | select #{N='ACL';E={ ($global:something = $_ |Get-Acl) }} | select ACL,#{N='RuleCount';E={$something.Access.Count}}
To explain why this is a bad idea, let's take this example instead:
function TimesTen {
1..10 | select #{N='Base';E={$global:v10 = $_ * 10; $_}} | select Base,#{N='Tenfold';E={$v10}}
}
This might appear to work as intended:
PS ~> TimesTen
Base Tenfold
---- -------
1 10
2 20
3 30
4 40
5 50
6 60
7 70
8 80
9 90
10 100
But this only works as long as pipeline output from Select-Object isn't buffered:
PS ~> $PSDefaultParameterValues['Select-Object:OutBuffer'] = 4
PS ~> TimesTen
Base Tenfold
---- -------
1 50
2 50
3 50
4 50
5 50
6 100
7 100
8 100
9 100
10 100
Oops! Now you need to explicitly prevent that:
function TimesTen {
1..10 | select #{N='Base';E={$global:v10 = $_ * 10; $_}} -OutBuffer 0 | select Base,#{N='Tenfold';E={$v10}}
}
With ForEach-Object, you avoid all of this.

How do I limit the results to 20?

Need to the limit the results of this to the first 20.
Is there another way I can loop this to get 20 results at a time?
$Array = #()
#Fill $Array with Data
$List = $Array | ForEach-Object {"$_`n"}
Personally, I would limit ther returned results prior to the looping construct. My example uses the names of service objects, but I needed something to use...
$TOlist = (Get-Service).Name
$TOlist | Select-Object -First 20 | ForEach-Object {
$_
}
You'll need to use at least two lines here. As far as I'm aware, you can't both assign your $TOlist variable and start sending each value down the pipeline.

Split hastable at key/value pair

How can I split a hashtable starting from a specific key/value pair?
I have hashtable like the following, just longer:
Name Value
---- -----
Name Alpha
Age 2
Position Trick
Start date 01-01-31
End date Unknown
Name Corax
Age 21
Position Sneak
Earnings 40'000
End Date Unknown
Name Horus
Age 22
Position Dead
Why Heresy
End date 03-30-30
I tried Group-Object but it failed.
I particularly wanted to separate it by Name and everything aside from Name, Age and Position are not consistent.
My actual issue is that I want to parse the hashtable for the Name and Age when Why = Heresy, and unfortunately, the original source of the data is a list of strings, which is the reason why I convert it to a hashtable.
Hashtables are not ordered, so you can't rely on a concept of "before" and "after". If you know the specific names of one complete set of keys, then you can loop through the hashtable and build two new ones, so if you wanted one hashtable to contain the Name, Age, and Position, and the other to contain everything else, you can do something like this:
$New1 = #{}
$New2 = #{}
$KeysGroup1 = #('Name','Age','Position') # This could just be one value
$MyHashTable.GetEnumerator().ForEach({
if ($_.Key -in $KeysGroup1) {
$New1[$_.Key] = $_.Value
} else {
$New2[$_.Key] = $_.Value
}
})
You can use an ordered dictionary if order is important for you. You can use a shortcut to create a literal ordered dictionary by preceding a literal hashtable with the [ordered] type accelerator:
$myOrdered = [ordered]#{ a = 1; z = 5; g = 2 }
From there you could do a similar approach to above that relies on order.
To create an ordered dictionary that isn't based on a literal:
$myOrdered = New-Object System.Collections.Specialized.OrderedDictionary
# or in PowerShell v5
$myOrdered = [System.Collections.Specialized.OrderedDictionary]::new()
$myOrdered.Add('key','value')
Edit based on comments
It sounds more like what you have is an array of hashtables and you now want to go about filtering these.
A [hashtable] can (and is often) used as a sort of proto-object, and it can be very useful for that because it often supports the same syntax, and it has built-in literal support.
But you're starting to run into their limits, and at this point I think you want to be dealing with an array of objects and not an array of hashtables.
Luckily, there are really easy ways to create objects in PowerShell right from a hashtable:
$obj = New-Object -TypeName PSObject -Property $myHash
$obj = [PSCustomObject]$myHash
$objArray = $myHashArray.ForEach({[PSCustomObject]$myHash})
Once you've got your array of objects, the real fun begins:
$heretics = $objArray.Where({$_.Why -eq 'Heresy'})
You'll notice I didn't even bother filtering out the other properties here. You shouldn't, until you really need to. Then you can use Select-Object or just access the properties you need. So for display purposes you might just do:
$heretics | Format-Table Name,Age
There's more stuff you can do with an object that you can't with hashtables, like add special types of properties:
$objArray | Add-Member -MemberType ScriptProperty -Name IsHeretic -Value { $this.Why -eq 'Heresy' } -Force
$heretics = $objArray.Where({$_.IsHeretic})
So, in the end; I figured something out, Thanks a lot to #briantist for guidance to the right direction.
My solution code is:
$startobj = (0..($hastable.count -1)) | where {$hashtable[$_].keys -like "*Name*"}
$endobj = (0..($hashtable.count -1)) | where {$hashtable[$_].keys -like "*End date*"}
$objindex = (0..($hashtable.count -1))
$numberofobjindex = 0..($hashtable.Where({$_.keys -like "*Name*"}).count - 1)
$hashtableparsed = foreach ($numba in $numberofobjindex) {
$Conv2data = $hashtable[($startobj[$numba]..$endobj[$numba])]
[PSCustomObject] #{
Name = $Conv2data.Name
Age = $Conv2data.Age
Why = $Conv2data.Why
}
}
$hashtableparsed
I realized that the hashtable data I was presented with had a repeating pattern with Name at the start of each cycle, and End date at the end.
So I basically did an indexing of the hashtable, and marked and indexed all instances where the cycle would start and end.
I then counted the cycles, and FOR EACH cycle captured the data of hashtable data in the lines particularly of that cycle and turned it into a PSCustomObject