Using a function in a calculated field in Powershell? - powershell

So I have this:
get-ADUser -SearchRoot 'boogers.com/Locations/Kleenex' |
Where-Object { $_.TITLE -ne 'Snot' } |
Select LastName,FirstName,Description,SamAccountName,Department,TITLE, #{Name='bs_value';Expression=#{Name='TITLE'; Expression={(Get-Culture).TextInfo.ToTitleCase($_.Title.ToLower())}}
And it works great, until, oh I dunno, I want another calculated field...then it becomes JUST TOO LONG!
So I write a function:
function tc($name, $str) {
return #{Name=$name; Expression={(Get-Culture).TextInfo.ToTitleCase($str.ToLower())}}
}
Seems reasonable enough, no?
But if I run the function:
$bob = tc 'Title' 'bob'
I get:
Name Value
---- -----
Expression (Get-Culture).TextInfo.ToTitleCase($str.ToLower())
Name Title
Instead of...
Name Value
---- -----
Expression "Bob"
Name Title
I just want the code to be shorter! I'm using a computer!

since you didn't show how you were calling the modified function i mentioned in my comment, i presume you were not using it correctly. [grin] your function is ... rather peculiar, so i rewrote it to work in a more normal manner.
function ConvertTo-TitleCase ([string]$InString)
{
(Get-Culture).TextInfo.ToTitleCase($InString.ToLower())
}
Get-LocalUser |
Where-Object {
$_.Name -notmatch "Admin|Guest|Home|$env:USERNAME"
} |
Select-Object -Property Name, FullName, Enabled,
#{Name='bs_value';Expression={ConvertTo-TitleCase -InString $_.Description}}
output from my local use list ...
Name FullName Enabled bs_value
---- -------- ------- --------
22 2 Digit 2 True The Digit 2 Twice
TooToo Too Also Too True The Word For Also, Repeated Twice
ToTo To Thataway To True The Destination Designator, Two Times.
Tutu Tutu Dress Tutu True The Ballet Apparel For Ladies.
TwoTwo Two Number Two True Repeating The Name Of The Number After One.

Related

How does pipeline affect Select-Object?

I'm trying to make a table in PowerShell with custom headers, I am able to do this with
"var" | Select #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
First Second Third
----- ------ -----
1 2 3
However, without the initial object, there is no output
Select #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
I can't tell the difference between these other than one is after a pipeline while the other isn't. Why won't this work?
Why doesn't this work?
The cmdlet Select-Object (alias Select) has one required parameter which is -inputObject Which also happens to be the object that gets passed through the pipeline.
Select-Object -InputObject "Example" -Property #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
Will have the output
First Second Third
----- ------ -----
1 2 3
While without -InputObject, it will not have an output because there is no input (Thanks #mklement0).
Select-Object "Example" -Property #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
# No Output
and with the pipeline, it will
"Example" | Select-Object "Example" -Property #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
First Second Third
----- ------ -----
1 2 3
The -inputObject Parameter will usually contain the table you would like to select columns from or other things (if you are not using expressions).
Although the answer from #Neko answers the exact question, I think it is important to mention that the Select-Object cmdlet is not mentioned to construct new custom objects:
Quote from the Get-Help Select-Object -Online:
The Select-Object cmdlet selects specified properties of an object
or set of objects.
In other words; the object needs to exist in order to select its properties.
For what you are doing with
"var" | Select #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}`
you're creating a new pscustomobject by removing all the default properties from the "Var" (string) object and adding new properties with a hard-coded (static) expression which is a long-winded syntax and quiet expensive for building a new object.
To construct a new pscustomobject ("table"), you can simply use the constructor syntax:
[pscustomobject]#{First = '1'; Second = '2'; Third = '3'}
For legacy (prior PSv3) POwerShell versions:
New-Object PSObject -Property #{First = '1'; Second = '2'; Third = '3'}

powershell - How refer custom expression property on single one select-object

can you help me with this simple example script?
How do I refer to "custom expression property" on single one "select-object"?
I would like: single "select-object"
Get-Process | Select-Object Id, 
    #{name="MyProp"; expression={$_.id}},
    #{name="MyPropRef"; expression={$_.MyProp}}
...but third property "MyPropRef" is not displayed!
Id MyProp MyPropRef
-- ------ ---------
3780 3780
While with double select-object piped, "MyPropRef" is displayed
Get-Process | Select-Object Id, 
    #{name="MyProp"; expression={$_.id}} | Select-Object *,
    #{name="MyPropRef"; expression={$_.MyProp}}
Id MyProp MyPropRef
-- ------ ---------
3780 3780 3780
Thanks
Calculated properties only operate on the input objects' original properties, they cannot refer to each other.
If you need to add additional properties based on previously calculated properties, you indeed need another (expensive) Select-Object call.
Your command:
Get-Process | Select-Object Id,
#{name="MyProp"; expression={$_.id}},
#{name="MyPropRef"; expression={$_.MyProp}}
creates a .MyPropRef property, but its value is $null, because the System.Diagnostics.Process instances that Get-Process outputs themselves do not have a .MyPropRef property.
If you want to make do with a single Select-Object call without repetition, you can define the script blocks used in the calculated properties in variables beforehand and reuse them across calculated property definitions; e.g.:
$myPropExpr = { $_.id }
Get-Process | Select-Object Id,
#{ name="MyProp"; expression=$myPropExpr },
#{ name="MyPropRef"; expression={ "!" + (& $myPropExpr) }}
This would yield something like:
Id MyProp MyPropRef
-- ------ ---------
3780 3780 !3780
...

How does $_ work when piping in PowerShell?

I'm confused how the $_ variable works in certain contexts of piping. In this example for backing up a Bitlocker key:
Get-BitlockerVolume | % {$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint}
This is how I read it in English:
Get all BitLockerVolume objects
For each BitLockerVolume object, pipe the KeyProtector fields forwards
Pipe KeyProtector objects forwards further for those with a RecoverPassword
Run the Backup-BitlockerKeyProtector, and supply the MountPoint
However, MountPoint is a field of the BitLockerVolume object, as shown here:
PS C:\Windows\system32> Get-BitLockerVolume | Get-Member | Where-Object {$_.Name -eq "MountPoint"}
TypeName: Microsoft.BitLocker.Structures.BitLockerVolume
Name MemberType Definition
---- ---------- ----------
MountPoint Property string MountPoint {get;}
So, for the entire block wrapped in brakcets { }, will the $_ variable ALWAYS be the same through any amount of piping? For example, the object we are piping forwards is changing. It's no longer a BitLockerVolume Object, but instead a KeyProtector object. So will the $_ always refer to the BitLockerVolume object in this case, or will it change further down the pipeline depending on different types of objects piped further through the chain?
So $_ is the info from the current pipe.
1,2 | %{
$_
}
response
1
2
while
1,2 | %{
"a","b" | %{
$_
}
}
response
a
b
a
b
We can see in the first that the output from %_ is from the last info given which is 1,2. While the next example still loops 1,2 but the output is from the pipe inside a,b.
There are ways around this by storing the first pipe information into a variable in the second pipe
1,2 | %{
$Num = $_
"a","b" | %{
$Num
}
}
which case the output is
1
1
2
2
In the example you gave lets look at it formated
Get-BitlockerVolume | % {
$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}
You have 2 different pipes. The First is getting 'BitlockerVolumevolume'.
The second starts with you sending the BitlockerVolume's KeyProtector.
Its like saying
For each Bitlocker volume, Get KeyProtector.
For each KeyProtector, Get me ones that have the member RecoveryPassword
Foreach KeyProtector with member RecoveryPassword, Backup Bitlocker Key Protector Using KeyProtector's Mountpoints
So on one final note I would also assume the example you gave wouldnt work.
What you might be looking for is this...
Get-BitlockerVolume | % {
$MountPoint = $_.MountPoint
$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_.KeyProtectorId
}
Let's expand the aliases and fill in the implied parameters. $_ can only be used inside script blocks '{ }' that are options to cmdlets. Just because you're in a pipe, doesn't mean you can use $_ . The $_ here belongs to Foreach-Object. Where-Object is using a comparison statement.
Get-BitlockerVolume | Foreach-Object -Process {
$_.KeyProtector | Where-Object -Property RecoveryPassword |
Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}
I know there are already good answers here, but I feel one important question was not addressed. The question of what happens to $_ throughout the Foreach-Object {} block when there is nesting. I am going use ArcSet's example since that was the answer selected.
1,2 | % {
"$_ before second foreach"
'a','b' | % {
"$_ inside second foreach"
}
"$_ after second foreach"
}
1 before second foreach
a inside second foreach
b inside second foreach
1 after second foreach
2 before second foreach
a inside second foreach
b inside second foreach
2 after second foreach
Notice that $_ becomes the current object being processed by the code within the Foreach-Object {} blocks. When entering the second Foreach-Object block, $_ changes. When exiting the second Foreach-Object block, $_ changes back to the object that will be continued to be processed by the remainder of the first block. So $_ neither remains the same nor is lost during the block processing. You will need to either assign $_ as another variable or in applicable situations use the -PipelineVariable switch to access those objects within different blocks.
Id' like to build on ArcSet's answer just a little bit. Since I finally understood the value of the $PSItem is changing when the type changes down the pipeline, I ran this code to do a little check.
Get-BitLockerVolume | % {$_.GetType()}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False BitLockerVolume System.Object
True False BitLockerVolume System.Object
True False BitLockerVolume System.Object
Here we can see some objects returned by the pipeline are of the BitLockerVolume type.
Now, based on my original question/example, if we pipe it further based on KeyProtector the object type will change for the $PSItem variable.
Get-BitLockerVolume | % { $_.KeyProtector | ? RecoveryPassword | % {$_.GetType()}}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False BitLockerVolumeKeyProtector System.Object
True False BitLockerVolumeKeyProtector System.Object
So at this point, at the end of the pipeline, we execute some other cmdlet like Backup-BitlockerKeyProtector and reference the $PSItem variable, aka $_, then it will refer to the object types last passed through the pipeline, in this case, the objects would be of the BitLockerVolumeKeyProtector type.

Why does the column name banner from GetType() not display each time

In the output, the header line of column names from Get_Type() displays only once. Why is that? Is there anything I can do to make it display in the output of GetType() every time?
PS C:\src\ps> Get-Content .\mva.ps1
$items = "Doug", "Finke", "NY", "NY", 10017
$FirstName, $LastName, $Rest = $items
$FirstName
$FirstName.GetType()
"==============================="
$LastName
$LastName.GetType()
"==============================="
$Rest
$Rest.GetType()
"==============================="
The output.
PS C:\src\ps> .\mva.ps1
Doug
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
===============================
Finke
True True String System.Object
===============================
NY
NY
10017
True True Object[] System.Array
===============================
PS C:\src\ps> $PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
5 0 10586 494
You can pass values to Format-Table (ft):
Get-Content .\mva.ps1
$items = "Doug", "Finke", "NY", "NY", 10017
$FirstName, $LastName, $Rest = $items
$FirstName;
$FirstName.GetType() | ft;
"==============================="
$LastName;
$LastName.GetType() | ft;
"==============================="
$Rest;
$Rest.GetType() | ft;
"==============================="
Paweł Dyl beat me to a similar answer but I want to expand on it a bit.
The PowerShell formatter looks at the first object it sees and decides how to display the output based on that type.
To work around that, you have to decide what you actually want to do.
First, understand that by default the output of that code is being sent down the pipeline, the equivalent of sending it all to Write-Output. If this is in a function for example, all of that data will be the return value (but NOT the headers / formatted information, that comes about after the function returns if nothing else is done with the data).
So if you use Format-Table as suggested, it will look correct, but you've fundamentally changed what's happening. In a function, you would be returning format objects; a very different thing. Another thing to note is that this will always show a table format, even if the default format would have been a list.
You could also pipe to Out-String, which would give you a string representation of the default format, but again you're returning a different thing: a formatted string.
You can pipe to Out-Host and this will work too, but critically, this information will not be returned to your pipeline (from the function), but it will be displayed on the screen.
So just consider what you really want and how the various options affect that.

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