Adding Header to the Variable - powershell

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

Related

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 to get Count of Values from a Hashtable per key in powershell

Currently I am able to get the list of values from a hashtable with the below cmdlet, but I would like get a count of values per key. Please lend me some advice, if this can be achieved using GetEnumerator method
$keys.GetEnumerator() | % {
[PSCustomObject]#{
key = $_.Value
}
}
My Hashtable $keys:
Name Value
---- -----
9 {G637A146}
-3 {F637A146, G637A146}
3 {F637A146, E637A146}
-2 {F637A146}
Expected Output Using GetEnumerator:
Key Value
---- -----
9 1
-3 2
3 2
-2 1
New Edit:
2. How to get the unique count of values as well?
My Hashtable $keys:
Name Value
---- -----
9 {G637A146, F637A146, J637A146}
-3 {F637A146, F637A146, G637A146, F637A146}
Expected Output Using GetEnumerator:
Key Value
---- -----
9 3
-3 2
Supposing your hashtable looks like this:
$keys = [ordered]#{
9 = 'G637A146'
-3 = 'F637A146', 'G637A146'
3 = 'F637A146', 'E637A146'
-2 = 'F637A146'
}
Then this should get you what you want:
$keys.GetEnumerator() | ForEach-Object {
[PSCustomObject]#{
Key = $_.Key
Value = ($_.Value).Count
}
}
Output:
Key Value
--- -----
9 1
-3 2
3 2
-2 1
Update
You could create a second hashtable where you keep track of the count values and only output if that vaue has not been seen before:
$seenThisBefore = #{}
$keys.GetEnumerator() | ForEach-Object {
$count = ($_.Value).Count
if (!$seenThisBefore.ContainsKey($count)) {
[PSCustomObject]#{
Key = $_.Key
Value = $count
}
$seenThisBefore[$count] = $true # add the count as key. The value here doesn't matter
}
}
I think you can cast the value to an array and then get the Count property.
So:
$keys.GetEnumerator() | % {
[PSCustomObject]#{
key = #($_.Value).Count
}
}
if I'm understanding your code correctly.
This is called the array sub-expression operator according to about_Arrays.
Since the comment response of the prior suggestion indicates that the format of the output for value isn't of some object but rather a string, then the value would need to be parsed.
Here's an example of how that might be done:
#('{a,b}' -replace '{', '' -replace '}', '' -split ',').Count
which produces the output of 2.
That just uses operators as documented on about_Operators

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

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.

Powershell counting lines in file incorrectly when there is only one line in the file?

I am writing a powershell script to calculate summary stats for a csv file with 100,000+ rows.
In my foreach loop, one of my lines is:
$count = $Not_10000.count
Where "$Not_10000" is the result after filtering a csv, which was read using the import-csv command and filtered using
where {$_.ifhighspeed -eq 10000}.
I found that the value of "$count" is correct whenever "$Not_10000" has more than one line. However, when "$Not_10000" only has one line, the result is that $count is empty. I tried going into the powershell prompt and doing
$count = $Not_10000 | Measure-Object -lines
But it shows 0 lines even though it has one line. The output of
$Not_10000[0]
is
DATE ENTITYNAME IFHIGHSPEED
---- ---------- -----------
8/25/2014 12:00:00 AM SF15-0326 1000
Why wouldn't this one line output be counted correctly? I manually changed the filters to make "$Not_10000" contain 15 lines, and this was counted correctly.
I seem to have trouble in general giving the full picture, so let me know if you need more info or clarification.
Matt gives a good answer, but is only kind of the answer to your question.
The .Count property is a member of the Array object type that is being referenced when the number of results is more than one. When you only have one result then what is returned is not an array, it is a String, or an Object, or an Integer or something, but not an array unless you specifically make it one. In order for this to always come back with a number:
$count = $Not_10000.count
You need to cast it as an array, which is most simply done by enclosing it in #()
$count = #($Not_10000).count
This is most easily seen by using the .PSObject member of any object, when used on an array. Let's create an array, and look at the Type to verify that it's an array, and then look at the PSObject members (filtering for just properties, since it has a lot of members we don't care about) for that array object.
PS C:\Users> $test = #("Red","Blue","Green")
PS C:\Users> $test.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\Users> $test.PSObject.Members | Where{$_.MemberType -match "property"}|FT Name,MemberType,Value,ReferencedMemberName
Name MemberType Value ReferencedMemberName
---- ---------- ----- --------------------
Count AliasProperty 3 Length
Length Property 3
LongLength Property 3
Rank Property 1
SyncRoot Property {Red, Blue, Green}
IsReadOnly Property False
IsFixedSize Property True
IsSynchronized Property False
Item ParameterizedProperty ...int index) {get;set;}
What we see here is that it is an AliasProperty for the member Length, which on an array gives the number of records, just like it's alias Count does.
Use the Count property of Measure-Object. Lines, Words, Characters are things you use to measure text not objects
$count = $Not_10000 | Measure-Object | Select-Object -ExpandProperty Count
or
$count = ($Not_10000 | Measure-Object).Count
More Explanation
I have a csv with a header and 7 entries.
Path Data Files
---- ---- -----
\\someserver\somepath1 100 1
\\someserver\somepath2 150 4
\\someserver\somepath1 200 5
\\someserver\somepath3 450 8
\\someserver\somepath4 200 23
\\someserver\somepath1 350 2
\\someserver\somepath2 800 9
When i do the following (-lines is not a valid switch for Measure-Object )
Import-Csv E:\temp\stack.csv | Where-Object{$_.Data -gt 300} | Measure-Object -line
Since this is not text there are no lines to measure. I get this output regardless of how many entries in the file or filtered object. You would expect 3 but you actually get 0
Lines Words Characters Property
----- ----- ---------- --------
0
If i read the file as text i would get a result for lines
Get-Content E:\temp\stack.csv | Measure-Object -line
Lines
-----
8

Replace values in hashtable in PowerShell

I have a hashtable in PowerShell that looks like this:
$table = #{
1 = 3;
2 = 3;
5 = 6;
10 = 12;
30 = 3
}
I need to replace all "3" values with "4".
Is there a nice and clean way to do this without iterating over each pair and writing each one to a new hashtable?
Could the action with the same data be done easier if I'd use some other .NET collection class?
This throws exception that "Collection was modified":
$table.GetEnumerator() | ? {$_.Value -eq 3} | % { $table[$_.Key]=4 }
This adds another "Values" member to the object and breaks it:
$table.Values = $table.Values -replace 3,4
You can't modify the table while iterating over it, so do the iteration first and then do the updates. Just split your pipeline in two:
PS>$change = $table.GetEnumerator() | ? {$_.Value -eq 3}
PS>$change | % { $table[$_.Key]=4 }
PS>$table
Name Value
---- -----
30 4
10 12
5 6
2 4
1 4
The above answer didn't work for me and I couldn't fit this as a comment. The above single-lined answer didn't do anything. I am trying to change a single value to "Off" based on my hashtable.Key aka Name. Notice where I wrote $(backtick) is supposed to be a literal backtick, but it was messing up the code block. Here is my hashtable that is pulled from .\BeepVariables.txt.
Name Value
---- ----
$varAwareCaseSound "On"
$varEmailSound "On"
function SetHash2([string]$keyword, [string]$value){
$hash = #{}
$hash = (get-content .\BeepVariables.txt).replace(";"," $(backtick) n") | ConvertFrom-StringData
#($hash.GetEnumerator()) | ?{$_.key -like "*$keyword*"} | %{$hash[$_.value]=$value}
$hash
}
SetHash2 "aware" '"Off"'