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

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!

Related

Why does select-object -first 1 not return an array for consistency?

When I pipe some objects to select-object -first n it returns an array except if n is 1:
PS C:\> (get-process | select-object -first 1).GetType().FullName
System.Diagnostics.Process
PS C:\> (get-process | select-object -first 2).GetType().FullName
System.Object[]
For consistency reasons, I'd have expected both pipelines to return an array.
Apparently, PowerShell chooses to return one object as object rather than as an element in an array.
Why is that?
Why questions are generally indeterminate in cases like this, but it mostly boils down to:
Since we asked for the "-first 1" we would expect a single item.
If we received an array/list we would still need to index the first one to obtain just that one, which is pretty much what "Select-Object -First 1" is designed to do (in that case.)
The result can always be wrapped in #() to force an array -- perhaps in the case where we've calculated "-First $N" and don't actually know (at that moment in the code) that we might receive only 1.
The designer/developer thought it should be that way.
It's #3 that keeps it from being an issue:
$PSProcess = #(Get-Process PowerShell | Select -First 1)
...this will guarantee $PSProcces is an array no matter what the count.
It even works with:
$n = Get-Random 3
#(Get-Process -first $n) # $n => 0, 1, or 2 but always returns an array.
The pipeline will return the [System.Diagnostics.Process] object. In your first example it's only one object. The second one is an [System.Object[]] array of the [System.Diagnostics.Process].
$a = (get-process | select-object -first 1)
$a | Get-Member
$b = (get-process | select-object -first 2)
,$b | Get-Member

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.

How to strip out everything but numbers in a variable?

I'm trying to automate setting an IP address through PowerShell and I need to find out what my interfaceindex number is.
What I have worked out is this:
$x = ( Get-NetAdapter |
Select-Object -Property InterfceName,InterfaceIndex |
Select-Object -First 1 |
Select-Object -Property Interfaceindex ) | Out-String
This will output:
InterfaceIndex
--------------
3
Now the problem, when I try to grab only the number using:
$x.Trim.( '[^0-9]' )
it still leave the "InterfaceIndex" and the underscores. This causes the next part of my script to error because I just need the number.
Any suggestions?
This will get your job done:
( Get-NetAdapter | Select-Object -Property InterfceName,InterfaceIndex | Select-Object -First 1 | Select-Object -Property Interfaceindex).Interfaceindex
Actually you do not need two times to select the property: do like this:
( Get-NetAdapter |Select-Object -First 1| Select-Object -Property InterfceName,InterfaceIndex).Interfaceindex
Answering your immediate question: you can remove everything that isn't a number from a variable by, well, removing everything that isn't a number (or rather digit):
$x = $x -replace '\D'
However, a better aproach would be to simply not add what you want removed in the first place:
$x = Get-NetAdapter | Select-Object -First 1 -Expand InterfaceIndex
PowerShell cmdlets usually produce objects as output, so instead of mangling these objects into string form and cutting away excess material you normally just expand the value of the particular property you're interested in.
(Get-NetAdapter | select -f 1).Interfaceindex
No point in selecting properties as they are there by default. If you want to keep object do:
(Get-NetAdapter | select -f 1 -ov 'variablename').Interfaceindex
where f = first, ov = outvariable
$variablename.Interfaceindex
You don't need Out-String as cast to string is implicit when you output to screen. and if you try to work with this data further down powershell is clever enough to cast it from int to string and vice versa when needed.

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.

Issue with Powershell custom table

I'm trying to create a custom table based on two other tables (csv-imported) - some kind of a VLOOKUP, but I can't seem to find a solution. I've come up with the following (failing) code:
$DrawPlaces | select Module, Workplace, #{ Name = "IPaddress"; Expression = {$Workstations.workstation.where($_.WorkPlace -eq $Workstations.Workplace)}} -First 15
Both Drawplaces and $Workplaces are PSCustomObject. The result of this would then go to another variable.
I'm not even sure the logic or syntax is correct, but the result table has the IPaddress column empty. I've also tried with -match instead of -eq.
This doesn't make sense: $Workstations.workstation.where($_.WorkPlace -eq $Workstations.Workplace)
.where() requires a scriptblock parameter like .where({}).
Keeping in mind that inside the where-statement $_ is refering to the current object in the $workstations.workstation-loop, your where-statement is testing ex. $workstations.workstation[0].workplace -eq $workstations.workplace. Is that really what you want?
Are you trying to achieve this?
$DrawPlaces |
Select-Object -First 15 -Property #(
"Module",
"Workplace",
#{ Name = "IPaddress"; Expression = {
#Save the Workspace-value for the current object from $DrawPlaces
$wp = $_.WorkPlace;
#Find the workstation with the same workplace as $wp
$Workstations | Where-Object { $_.WorkPlace -eq $wp} | ForEach-Object { $_.Workstation }
}
}
)