Split multiple items in a string - powershell

Get-EC2Instance |%{ $.RunningInstance } | select-object InstanceId,#{Name='Key'; Expression={$.Tag.Key} },#{Name='Value'; Expression={$_.Tag.Value} }
This command displays below output. Each InstanceID has multiple key and multiple values.
how do I rewrite the command to split each key string and value string so the output displays only one item, for example:

You can just pipe what you have into a ForEach loop, and then inside that do a For loop for each key/value pair like:
Get-EC2Instance |Select -ExpandProperty RunningInstance |
select-object InstanceId,#{Name='Key'; Expression={$.Tag.Key} },#{Name='Value'; Expression={$_.Tag.Value} } |
ForEach{
$CurInst = $_
For($i=0;$i -lt $CurInst.Key.Count;$i++){
New-Object PSObject -Prop#{
'InstanceId' = $CurInst.InstanceId
'Key' = $CurInst.Key[$i]
'Value' = $CurInst.Value[$i]
} #End object properties
} #End For Loop
} #End ForEach Loop
That should output like what you want.

Related

Powershell Pipeline - return a new Object, that was created within pipline

I keep running into the same problem again, and i have my default way of handling it, but it keeps bugging me.
Isn't there any better way?
So basicly i have a pipline running, do stuff within the pipline, and want to return a Key/Value Pair from within the pipline.
I want the whole pipline to return a object of type psobject (or pscustomobject).
Here is the way i do it everytime.
I create a hashtable at the beginning of the pipline and add key/Value Pairs from within the pipline to this hashtable using the .Add() method.
Afterwards i create a psobject by passing the hashtbale to New-Object`s -Property Parameter. This gives me the desired result.
Get-Process | Sort -Unique Name | ForEach-Object -Begin { $ht = #{} } -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key,$val)
}
# Create PSObject from Hashtable
$myAwesomeNewObject = New-Object psobject -Property $ht
# Done - returns System.Management.Automation.PSCustomObject
$myAwesomeNewObject.GetType().FullName
But this seems a bit cluncky, isn't there a more elegant way of doing it?
Something like this:
[PSObject]$myAwesomeNewObject = Get-Process | Sort -Unique Name | ForEach-Object -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# return Key/Val Pair
#{$key=$val}
}
# Failed - returns System.Object[]
$myAwesomeNewObject.GetType().FullName
This unfortunally dosn't work, since the pipe returns an array of hashtables, but i hope you know now what iam trying to achieve.
Thanks
Not sure if this is more elegant but just another way of doing it, this uses an anonymous function so $ht will no longer be available after execution, and casts to [pscustomobject] instead of using New-Object:
[pscustomobject] (Get-Process | Sort -Unique Name | & {
begin { $ht = #{ } }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key, $val)
}
end { $ht }
})
You can also use the -End parameter to convert the final hash table to a pscustomobject as part of the pipeline, without needing to set the whole thing to a variable
$ht[$key]=$val is also a nice shorthand for $ht.Add($key,$val):
Get-Process |
Sort -Unique Name |
Foreach -Begin { $ht = #{} } -Process {
$ht[$_.Name] = $_.Id
} -End {[pscustomobject]$ht} |
## continue pipeline with pscustomobject
Thanks to #Santiago Squarzon and #Cpt.Whale answers, i were able to combine them to create a solution that pleases me:
$myAwesomeNewObject = `
Get-Process | Sort -Unique Name | & {
begin { $ht = #{} }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht[$key]=$val
}
end {[pscustomobject]$ht}
}
# Success - System.Management.Automation.PSCustomObject
$myAwesomeNewObject.Gettype().FullName
# And helper Hashtable is NULL thanks to the
# anonym function
$null -eq $ht
Thanks alot Guys
Alternatively you may create a hashtable using Group-Object -AsHashTable:
# Store the PIDs of all processes into a PSCustomObject, keyed by the process name
$processes = [PSCustomObject] (Get-Process -PV proc |
Select-Object -Expand Id |
Group-Object { $proc.Name } -AsHashtable)
# List all PIDs of given process
$processes.chrome
Notes:
Common parameter -PV (alias of -PipelineVariable) makes sure that we can still access the full process object from within the calculated property of the Group-Object command, despite that we have a Select-Object command in between.
The values of the properties are arrays, which store the process IDs of all instances of each process. E. g. $processes.chrome outputs a list of PIDs of all instances of the chrome process.

How do I identify hashtable keys with multiple values in powershell

I have a hashtable created from an array like this:
$employeesHashtable = $employees | Group-Object -Property Initials -AsHashTable
How do I find keys having multiple values?
The alternative to using .GetEnumerator() can be using the hash table Keys, the key collection implements ICollection and can be enumerated without issues:
$keysWithMultipleValues = $employeesHashtable.Keys.where{ $employeesHashtable[$_].Count -gt 1 }
I ended up with this:
$keysWithMultipleValues = $employeesHashtable.GetEnumerator() | `
ForEach-Object { [PSCustomObject]#{ Key = $_.Key; Count = $_.Value.Count } } | `
Where-Object { $_.Count -gt 1 }

PowerShell - Convert Property Names from Pascal Case to Upper Case With Underscores

Let's say I have an object like this:
$test = #{
ThisIsTheFirstColumn = "ValueInFirstColumn";
ThisIsTheSecondColumn = "ValueInSecondColumn"
}
and I want to end up with:
$test = #{
THIS_IS_THE_FIRST_COLUMN = "ValueInFirstColumn";
THIS_IS_THE_SECOND_COLUMN = "ValueInSecondColumn"
}
without manually coding the new column names.
This shows me the values I want:
$test.PsObject.Properties | where-object { $_.Name -eq "Keys" } | select -expand value | foreach{ ($_.substring(0,1).toupper() + $_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()} | Out-Host
which results in:
THIS_IS_THE_FIRST_COLUMN
THIS_IS_THE_SECOND_COLUMN
but now I can't seem to figure out how to assign these new values back to the object.
You can modify hashtable $test in place as follows:
foreach($key in #($test.Keys)) { # !! #(...) is required - see below.
$value = $test[$key] # save value
$test.Remove($key) # remove old entry
# Recreate the entry with the transformed name.
$test[($key -creplace '(?<!^)\p{Lu}', '_$&').ToUpper()] = $value
}
#($test.Keys) creates an array from the existing hashtable keys; #(...) ensures that the key collection is copied to a static array, because using the .Keys property directly in a loop that modifies the same hashtable would break.
The loop body saves the value for the input key at hand and then removes the entry under its old name.[1]
The entry is then recreated under its new key name using the desired name transformation:
$key -creplace '(?<!^)\p{Lu} matches every uppercase letter (\p{Lu}) in a given key, except at the start of the string ((?<!^)), and replaces it with _ followed by that letter (_$&); converting the result to uppercase (.ToUpper()) yields the desired name.
[1] Removing the old entry before adding the renamed one avoids problems with single-word names such as Simplest, whose transformed name, SIMPLEST, is considered the same name due to the case-insensitivity of hasthables in PowerShell. Thus, assigning a value to entry SIMPLEST while entry Simplest still exists actually targets the existing entry, and the subsequent $test.Remove($key) would then simply remove that entry, without having added a new one.
Tip of the hat to JosefZ for pointing out the problem.
I wonder if it is possible to do it in place on the original object?
($test.PsObject.Properties|Where-Object {$_.Name -eq "Keys"}).IsSettable says False. Hence, you need do it in two steps as follows:
$test = #{
ThisIsTheFirstColumn = "ValueInFirstColumn";
ThisIsTheSecondColumn = "ValueInSecondColumn"
}
$auxarr = $test.PsObject.Properties |
Where-Object { $_.Name -eq "Keys" } |
select -ExpandProperty value
$auxarr | ForEach-Object {
$aux = ($_.substring(0,1).toupper() +
$_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()
$test.ADD( $aux, $test.$_)
$test.Remove( $_)
}
$test
Two-step approach is necessary as an attempt to perform REMOVE and ADD methods in the only pipeline leads to the following error:
select : Collection was modified; enumeration operation may not execute.
Edit. Unfortunately, the above solution would fail in case of an one-word Pascal Case key, e.g. for Simplest = "ValueInSimplest". Here's the improved script:
$test = #{
ThisIsTheFirstColumn = "ValueInFirstColumn";
ThisIsTheSecondColumn = "ValueInSecondColumn"
Simplest = "ValueInSimplest" # the simplest (one word) PascalCase
}
$auxarr = $test.PsObject.Properties |
Where-Object { $_.Name -eq "Keys" } |
select -ExpandProperty value
$auxarr | ForEach-Object {
$aux = ($_.substring(0,1).toupper() +
$_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()
$newvalue = $test.$_
$test.Remove( $_)
$test.Add( $aux, $newvalue)
}
$test
This seems to work. I ended up putting stuff in a new hashtable, though.
$test = #{
ThisIsTheFirstColumn = "ValueInFirstColumn";
ThisIsTheSecondColumn = "ValueInSecondColumn"
}
$test2=#{}
$test.PsObject.Properties |
where-object { $_.Name -eq "Keys" } |
select -expand value | foreach{ $originalPropertyName=$_
$prop=($_.substring(0,1).toupper() + $_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()
$test2.Add($prop,$test[$originalPropertyName])
}
$test2

How to iterate over the properties of an object

I am trying to write a PowerShell script that iterates through the objects properties and outputs only the ones that have a value of True.
My starting data is this:
UserId : 00546000000m3vCAAQ
UserPermissionsOfflineUser : True
UserPermissionsMobileUser : False
UserPermissionsSupportUser : True
UserPermissionsWebUser : False
I would like the output to consist of the UserID and only the licenses that are true like the following:
UserId : 00546000000m3vCAAQ
UserPermissionsOfflineUser : True
UserPermissionsSupportUser : True
I think I need two loops, one to iterate over each user and then another loop to iterate over the user's properties and just parse out all the false values.
foreach ($_ in $resultset)
{
$_ |
Select-Object -Property #{ N = 'UserId'; E = { $_.Id } }
#This is where I am getting stuck on the second loop.
#$_.psobject.properties | % { $_.Value }
}
Use Where-Object to find the properties with a value of $true, then use Select-Object to select those along with the UserID property:
$resultSet |ForEach-Object {
$trueProperties = $_.psobject.Properties |Where-Object {$_.Value -eq $true} |Select -ExpandProperty Name
$_ |Select -Property #('Id';$trueProperties)
}
Be aware that selectively picking out properties based on their value like this may cause you pain later on with Export-* or Format-* cmdlets
The |Select-Object -ExpandProperty Name part grabs the values of the Name property of the properties that made it through our Where-Object filter, so at this point $trueProperties contain 2 strings: "UserPermissionsOfflineUser" and "UserPermissionsSupportUser".
The expression #('Id';$trueProperties) simply concatenates the strings to the "Id" string, so the last Select-Object statement is basically the same as saying:
$_ |Select Id,UserPermissionsOfflineUser,UserPermissionsSupportUser

Hashtable with multiple values in GridView

I am storing data in a hashtable with multiple values like this:
$hash = #{}
$folders = dir (...) | where (...)
foreach ($folder in $folders) {
$num1 = (...)
$num2 = (...)
$hash.Add($folder.Name,#($num1,$num2))
}
So this is a hash with an array in its value part. The array always got two items. When finished the foreach part I want to show the data with Out-GridView like this:
$hash | select -Property #{Expression={$_.Name};Label="FolderName"},
#{Expression={$_.Name[0]};Label="num1"},
#{Expression={$_.Name[1]};Label="num2"} | Out-GridView
But as you can imagine, this is not working. How can I split the stored array in the value part of my hash into two new columns to show them in overall three columns in the GridView?
Should be something like Name, Value1, Value2 ...
And then multiple items which are stored in the hashtable as multiple rows.
Hashtables are not lists of objects with a Name and a Value property. That's just how PowerShell displays the data structure for your convenience. For processing a hashtable the way you tried you need an enumerator to produce such objects:
$hash.GetEnumerator() |
Select-Object #{n='FolderName';e={$_.Name}},
#{n='num1';e={$_.Value[0]}},
#{n='num2';e={$_.Value[1]}} |
Out-GridView
Or you can enumerate the keys of the hashtable, use them as the current objects in the pipeline, and look up the values by the respective key and index:
$hash.Keys |
Select-Object #{n='FolderName';e={$_}},
#{n='num1';e={$hash[$_][0]}},
#{n='num2';e={$hash[$_][1]}} |
Out-GridView
If you don't know the number of array elements beforehand you need an inner loop for processing the nested arrays, e.g. like this:
$hash.Keys | ForEach-Object {
$o = New-Object -Type PSObject -Property #{ 'FolderName' = $_ }
$a = $hash[$_]
for ($i = 1; $i -le $a.Count; $i++) {
$o | Add-Member -Type NoteProperty -Name "num$i" -Value $a[$i-1]
}
$o
} | Out-GridView
If you have a variable number of array elements, beware that PowerShell determines by the first object which properties will be displayed.