Parse string in Powershell - powershell

Learning powershell, trying to find out how to parse the first value from this resultset (10.60.50.40):
IPAddresses
-----------
{10.60.50.40, fe80::5ddf:a8f4:e29c:b66}
Normally I would just look it up, however, I don't know if {x, x} is a standard datatype of sorts in Powershell land.
Do I have to do rough string parsing, or is there some standard command to extract the first one, such as:
... | Select-Object IPAddresses | Select-String [0]
(I just made the select string part up. I'm lost.)

This is most likely the result of of the IPAddresses property of your object containing an array. The output you're seeing is stylized for display purposes, so it's not a string you would have to parse. Assuming your object is $obj, you should be able to do either of these:
$obj.IPAddresses[0]
$obj.IPAddresses | Select-Object -First 1

One solution is to use split function to convert the string into array and work with that like in the next steps:
Split the string into an array using the split function (comma is the item delimiter).
Grab the first item of the array (or whatever needed) and then also sanitize it (remove unnecessary curly bracket).
Example below:
$str = "{10.60.50.40, fe80::5ddf:a8f4:e29c:b66}"
$strArr = $str.Split(",")
Write-Host $strArr[0].Replace("{", "")

This is what I ended up doing:
$str = ... | Select-Object IPAddresses | ForEach {$_.IpAddresses}
Write-Host $str[0]

Depending on the source of your IPAddresses, this might not be optimal. You might get multiple IPAddresses per devices.
You might want to combine both approaches:
$str = ... | Select-Object -ExpandProperty IPAddresses | Select-Object -First 1
This will return the First IP address in your list per device.

Related

Why is this not working? - Trying to save properties in a variable for use multiple times in a function

I am trying to find a way to save the properties for a select statement in PowerShell but it isn't working. I haven't found a way to make an entire statement a literal so that it isn't reviewed until the variable is opened.
Here is what works:
$wsus.GetSummariesPercomputerTarget($CurrentMonthUpdateScope, $ComputerScope) |
Select-Object #{L="WSUSServer";E={$Server}},
#{L="FromDate";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString("MM/dd/yyyy")}},
#{L="ToDate";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString("MM/dd/yyyy")}},
#{L='Computer';E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
DownloadedCount,
NotInstalledCount,
InstalledPendingRebootCount,
FailedCount,
Installedcount |
Sort-Object -Property "Computer"
and I am trying to get the properties mentioned (starting just after the Select-Object statement and ending just before the last pipe) placed in a variable so that I can use the same properties multiple times with different scopes.
I have tried this:
$Properties = '#{L="WSUSServer";E={$Server}},
#{L="FromDate";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString("MM/dd/yyyy")}},
#{L="ToDate";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString("MM/dd/yyyy")}},
#{L="Computer";E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
DownloadedCount,
NotInstalledCount,
InstalledPendingRebootCount,
FailedCount,
Installedcount'
$wsus.GetSummariesPercomputerTarget($CurrentMonthUpdateScope, $ComputerScope) |
Select-Object $Properties |
Sort-Object -Property "Computer"
While this runs it doesn't give any data and I think it confuses PowerShell.
This gives the same response:
$Properties = "#{L=`"WSUSServer`";E={$Server}},
#{L=`"FromDate`";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString(`"MM/dd/yyyy`")}},
#{L=`"ToDate`";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString(`"MM/dd/yyyy`")}},
#{L=`"Computer`";E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
DownloadedCount,
NotInstalledCount,
InstalledPendingRebootCount,
FailedCount,
Installedcount"
Any options, thoughts, etc.?
The -Property argument of Select-Object expects an array, not a string. So something like this:
$Properties = #(#{L="WSUSServer";E={$Server}},
#{L="FromDate";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString("MM/dd/yyyy")}},
#{L="ToDate";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString("MM/dd/yyyy")}},
#{L="Computer";E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
"DownloadedCount",
"NotInstalledCount",
"InstalledPendingRebootCount",
"FailedCount",
"Installedcount")
Note, you will need to turn the simple property names into strings within your array.

Handle output that can either be an array or a single string

I have script which retrieves data from a web service in XML format. Certain elements can be there - or not - and can contain one or more sub elements. Therfore I retrieve the value with this:
$uid = $changeRecord.newAttrs | Where{$_.name -eq 'uid'} | Select -ExpandProperty Values | select -Index 0
This works fine. Mostly there is only one sub element in the <values> part of the answer and even if not, I am only interested in the first one. However, the last part | select -Index 0 produces silent warnings into the Windows Event Log (see also here ) if there is only one element within <values>. Therefore I would like to get rid of the error.
So I am looking for a way to achieve the same behaviour without it throwings errors - and possible not just put try-catch around.
Thanks!
// Update: As discussed below, the answers presented so far do not solve the issue. The closest by now is
([array]($changeRecord.newAttrs | Where{$_.name -eq 'uid'} | Select -ExpandProperty Values))[0]
This, however, fails with an error if the array does not contain any elements. Any idea if this can be handled as well within one line?
Have you tried this: Select-Object -First 1 ?
$uid = $changeRecord.newAttrs |
Where-Object {$_.name -eq 'uid'} |
Select-Object -ExpandProperty Values |
Select-Object -First 1
Select -Index n is only meant to be used on arrays as it will explicitly select from that index in the array. Therefore you will have issues when doing it on a single object. Select -First n will get you n number of objects off the pipeline.
All that said, when I am calling a command and the results may either be a single item or an array of items, I generally declare the variable as an array or cast the value as an array and then even if I get a single object back from the command it will be stored in an array. That way no matter what gets returned, I am treating it the same way. So in your case:
$uid = [array]($changeRecord.newAttrs | Where{$_.name -eq 'uid'} | Select -ExpandProperty Values) | select -Index 0
So, I finally found a solution which is probably fine for me:
$valueArray = [array]($changeRecord.newAttrs | Where{$_.name -eq 'uid'} | Select -ExpandProperty Values)
if(($valueArray -ne $null) -and ($valueArray.Count -gt 0))
{
$value = $valueArray.GetValue(0)
}
else
{
$value = "null..."
}
I put the whole thing into an array first and then check if the array contains any elements. Only if so, I get the first value.
Thanks for everybodys help!

Powershell counting same values from csv

Using PowerShell, I can import the CSV file and count how many objects are equal to "a". For example,
#(Import-csv location | where-Object{$_.id -eq "a"}).Count
Is there a way to go through every column and row looking for the same String "a" and adding onto count? Or do I have to do the same command over and over for every column, just with a different keyword?
So I made a dummy file that contains 5 columns of people names. Now to show you how the process will work I will show you how often the text "Ann" appears in any field.
$file = "C:\temp\MOCK_DATA (3).csv"
gc $file | %{$_ -split ","} | Group-Object | Where-Object{$_.Name -like "Ann*"}
Don't focus on the code but the output below.
Count Name Group
----- ---- -----
5 Ann {Ann, Ann, Ann, Ann...}
9 Anne {Anne, Anne, Anne, Anne...}
12 Annie {Annie, Annie, Annie, Annie...}
19 Anna {Anna, Anna, Anna, Anna...}
"Ann" appears 5 times on it's own. However it is a part of other names as well. Lets use a simple regex to find all the values that are only "Ann".
(select-string -Path 'C:\temp\MOCK_DATA (3).csv' -Pattern "\bAnn\b" -AllMatches | Select-Object -ExpandProperty Matches).Count
That will return 5 since \b is for a word boundary. In essence it is only looking at what is between commas or beginning or end of each line. This omits results like "Anna" and "Annie" that you might have. Select-Object -ExpandProperty Matches is important to have if you have more than one match on a single line.
Small Caveat
It should not matter but in trying to keep the code simple it is possible that your header could match with the value you are looking for. Not likely which is why I don't account for it. If that is a possibility then we could use Get-Content instead with a Select -Skip 1.
Try cycling through properties like this:
(Import-Csv location | %{$record = $_; $record | Get-Member -MemberType Properties |
?{$record.$($_.Name) -eq 'a';}}).Count

Select Method returns strings like #{Name=

I'd like to select items from the pipeline using "select" but it returns raw data like: #{Name=MyMachine}
This will not be helpful if, say I want to reboot #{Name=MyMachine} because there is not machine named #{Name=MyMachine}
There's a "MyMachine" but the script does not return just the name.
How do you strip out the "#{Name="}" when selecting individual objects?
Use the -ExpandProperty parameter of Select-Object:
$someobjects | Select-Object -ExpandProperty Name
Another option is echoing the property in a loop:
$something | % { $_.Name }

PowerShell: Format-Table without headers

In a PowerShell script, I have some objects that I pass to the Format-Table CmdLet.
The output of my script looks like this:
Something...
Operation AttributeName AttributeValue
--------- ------------- --------------
Delete Member John Doe
Something else...
Since the meaning of the fields is pretty self-explanatory, I would like to remove the headers, the '---' separators and the blank lines at the beginning and at the end from the output of Format-Table.
I don't think that the CmdLet supports this (or at least if there's a parameter to do this I couldn't find it).
What would the best way to leave only the lines with the actual values from the output of Format-Table?
Try the -HideTableHeaders parameter to Format-Table:
gci | ft -HideTableHeaders
(I'm using PowerShell v2. I don't know if this was in v1.)
Try -ExpandProperty. For example, I use this for sending the clean variable to Out-Gridview -PassThru , otherwise the variable has the header info stored. Note that these aren't great if you want to return more than one property.
An example:
Get-ADUser -filter * | select name -expandproperty name
Alternatively, you could do this:
(Get-ADUser -filter * ).name
The -HideTableHeaders parameter unfortunately still causes the empty lines to be printed (and table headers appearently are still considered for column width). The only way I know that could reliably work here would be to format the output yourself:
| % { '{0,10} {1,20} {2,20}' -f $_.Operation,$_.AttributeName,$_.AttributeValue }
Here is how I solve this. I just pipe the output to Out-String and then pass that output to the .NET Trim function:
(gci | ft -HideTableHeaders | Out-String).Trim()
This will strip out the line breaks before and after the table.
You can also use TrimStart to just take care of the header's line break if you still want the trailing line breaks.
(gci | ft -HideTableHeaders | Out-String).TrimStart()
Another approach is to use ForEach-Object to project individual items to a string and then use the Out-String CmdLet to project the final results to a string or string array:
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String
#Result: One multi-line string equal to:
#"
CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0
CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d
CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b
CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426
"#
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String -Stream
#Result: An array of single line strings equal to:
#(
"CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0",
"CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d",
"CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b",
"CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426")
The benefit of this approach is that you can store the result to a variable and it will NOT have any empty lines.
I know it's 2 years late, but these answers helped me to formulate a filter function to output objects and trim the resulting strings. Since I have to format everything into a string in my final solution I went about things a little differently.
Long-hand, my problem is very similar, and looks a bit like this
$verbosepreference="Continue"
write-verbose (ls | ft | out-string) # this generated too many blank lines
Here is my example:
ls | Out-Verbose # out-verbose formats the (pipelined) object(s) and then trims blanks
My Out-Verbose function looks like this:
filter Out-Verbose{
Param([parameter(valuefrompipeline=$true)][PSObject[]]$InputObject,
[scriptblock]$script={write-verbose "$_"})
Begin {
$val=#()
}
Process {
$val += $inputobject
}
End {
$val | ft -autosize -wrap|out-string |%{$_.split("`r`n")} |?{$_.length} |%{$script.Invoke()}
}
}
Note1: This solution will not scale to like millions of objects(it does not handle the pipeline serially)
Note2: You can still add a -noheaddings option.
If you are wondering why I used a scriptblock here, that's to allow overloading like to send to disk-file or other output streams.