Unable to access array variable - powershell

I am using the Invoke-Ping function (found here: https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a), which is working great. It creates output that looks like this:
> $Info = Invoke-Ping $ComputerNames -ErrorAction SilentlyContinue
> $Info[0..2]
Address : Machine1
IPV4Address : 10.10.44.213
IPV6Address :
ResponseTime : 0
STATUS : Responding
Address : Machine2
IPV4Address : 10.10.4.46
IPV6Address :
ResponseTime : 0
STATUS : Responding
Address : Machine3
IPV4Address : 10.10.4.58
IPV6Address :
ResponseTime : 0
STATUS : Responding
The problem I'm running into is when I try to do $Info.Address to output machine names. When I type $Info.Address I get
OverloadDefinitions
-------------------
System.Object&, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Address(int )
I'm sure it's because .Address is already defined but I don't know how to work around this and get to the actual value I need - My object's Address value. I'm sure it's simple but I'm just ignorant... What's the trick to get to my value?

use this command this may help you.
$Info | %{$_.Address}

This is an interesting one. It looks like you've found a bug in the Test-Connection cmdlet. That is what Invoke-Ping uses to ping the computers.
PetSerAl is correct. You can use ForEach to get the correct output. Alternatively you could also manually specify the item in the array that you are looking for. Example:
#Display Address of First item
$info[0].Address
#Display Address of All items
$info | Foreach {$_.Address}
#or
for ($i=0; $i -lt $info.Count; $i++) { $info[$i].Address }

Interesting, haven't run into a case of this, but it appears the root cause is that an array has an Address method.
You can verify like this: Get-Member -InputObject #(), or, with your code, Get-Member -InputObject $Info.
Why is this happening? While it's quite convenient, we're relying on a language feature that will unravel a property from an array, and properties / methods directly on the array will take precedence. This means we need to resort to the various workarounds folks have answered with.
Cheers!

Related

Powershell export nested object arrays into CSV

When i look into my object arra and type a $obj[2] (as exam) I'm getting
Name : audit_event
UUID : c6479a6f-f1bd-4759-9881-fcb493821aff
Timestamp : 17-06-20 13:30:48 +00:00
Fields : {[audit_schema_version, 1], [event_time, 17-06-20 13:30:48 +00:00], [sequence_number, 1], [action_id, 541934402]...}
Actions : {}
I would like to get all fields into a single csv file.
So first I started to find at least the fields, but dispite some solutions I saw i'm not getting it OK.
foreach ($UUID in $logsOBJECT[2].UUID) {
echo $UUID
foreach ($field in $logsOBJECT.$UUID.Keys) {
echo $field
}
}
This doesn't work.
I'm not a Powershell developer so quite novice.
I have to use Powershell because Synaps Analytics doesn't give a better option to read and process its logfiles.
Kind regards, Harry
Given an object that looks like this in JSON:
[{
"Name": "audit_event",
"UUID": "c6479a6f-f1bd-4759-9881-fcb493821aff",
"Timestamp": "17-06-20 13:30:48 +00:00",
"Fields": [["audit_schema_version", 1], ["event_time", "17-06-20 13:30:48 +00:00"], ["sequence_number", 1], ["action_id", 541934402]],
"Actions": {}
}]
(You can generate that by using $obj | ConvertTo-Json so it's easier for others to reproduce)
First we loop through the $obj list by passing it to ForEach-Object, or % for short. For each element we create a $result object that contains all the data, except the Fields property.
Then we loop through the fields property on the object. Each field is itself a list, where the first (0) element is the name, and the second (1) is the value. For each field, we add a property to the $result object with the name and value of the field.
When this is done we return the $result object to the pipeline, and that gets passed to Export-Csv which writes it to a file.
$obj | % {
$result = $_ | Select-Object * -ExcludeProperty Fields
$_.Fields | % {
$result | Add-Member -Name $_[0] -Value $_[1] -MemberType NoteProperty
}
return $result
} | Export-Csv -Path C:\test.csv -Encoding UTF8 -NoTypeInformation
I don't have your exact PS module of SynapsAnalystics...
But it seems to be a problem accessing nested arrays in Powershell.
Here I have an example with Windows services:
PS C:\WINDOWS\system32> $servic[20] | fl
Name : BrokerInfrastructure
DisplayName : Background Tasks Infrastructure Service
Status : Running
DependentServices : {workfolderssvc, WMPNetworkSvc, WSearch, embeddedmode}
ServicesDependedOn : {DcomLaunch, RpcSs, RpcEptMapper}
CanPauseAndContinue : False
CanShutdown : False
CanStop : False
ServiceType : Win32ShareProcess
Here, if I want the output of $servic.DependentServices I need to know the Keys\Propertys of $servic.DependentServices. ie,
You can get that by :
PS C:\WINDOWS\system32> $servic[20].DependentServices
Status Name DisplayName
------ ---- -----------
Stopped workfolderssvc Work Folders
Running WMPNetworkSvc Windows Media Player Network Sharin...
Running WSearch Windows Search
Stopped embeddedmode Embedded Mode
So the Propertys here are
Status Name DisplayName etc...
$servic[20].DependentServices would be similar to $obj[2].Fields in your case.
Try and see the Keys or Property's within $obj[2].Fields then decide which Property you want to loop through.
you can get that with
$obj[2].Fields | get-Module (this will give all parameters)
Then you can loop through the required Properties, like in my case:
foreach ($echserv in $servic[0-2])
{
write-host "*****************Service******************"
echo $echserv.Name
Write-Host "####DependentServices####"
foreach ($echDependServic in $servic.DependentServices.DisplayName)
{
echo $echDependServic
}
}
which would give output:
*****************Service******************
XboxGipSvc
####DependentServices####
Smartlocker Filter Driver
Agent Activation Runtime_ea2d3
Agent Activation Runtime
Windows Audio
Agent Activation Runtime_ea2d3
Agent Activation Runtime
Xbox Live Networking Service
.
.
.
Hope this helps with your problem.
PS: There are better ways to display your output using hashtables in Powershell. This can later be used to export to CSV etc..

Invoke-Command on remote session returns local values

Question
Should the script block of Invoke-Command, when run with a PSSession, always run on the remote computer?
Context
I ran the below powershell against a list of servers:
Clear-Host
$cred = get-credential 'myDomain\myUsername'
$psSessions = New-PSSession -ComputerName #(1..10 | %{'myServer{0:00}' -f $_}) -Credential $cred
Invoke-Command -Session $psSessions -ScriptBlock {
Get-Item -Path 'HKLM:\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters'
} | Sort-Object PSComputerName
# $psSessions | Remove-PSSession
This returned:
Hive: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos
Name Property PSComputerName
---- -------- --------------
Parameters MaxPacketSize : 1 myServer01
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer02
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer03
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer04
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer05
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer06
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer07
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer08
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer09
MaxTokenSize : 65535
Parameters MaxPacketSize : 1 myServer10
MaxTokenSize : 65535
All looks good; onlyl I'd not expected to see these values / I was running this as a quick sense check before setting the values on these servers to ensure I didn't overwrite anything.
I had a quick look at one of the servers using regedit; and found that MaxTokenSize and MaxPacketSize did not exist.
I then amended the command to use Get-ItemProperty instead of Get-Item:
Invoke-Command -Session $psSessions -ScriptBlock {
Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters' -Name 'MaxTokenSize'
} | Sort-Object PSComputerName
This time I got 10 errors:
Property MaxTokenSize does not exist at path HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters.
+ CategoryInfo : InvalidArgument: (MaxTokenSize:String) [Get-ItemProperty], PSArgumentException
+ FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
+ PSComputerName : myServer01
# ... (and the same for the other 9 servers, with only PSComputerName changing)
Regarding where the values that were returned came from... they're from my local machine. Amending my local registry entries and rerunning the original command showed all "servers" as having the new value.
I'm guessing this is a bug; but because I've not played with PSSessions much so far wanted to check here in case it's an issue with my understanding / usage of these commands, or if there are known gotchas to watch out for when using PSSessions.
tl;dr:
The root cause is a bug in the formatting instructions for registry keys (as of Windows PowerShell 5.1.18362.125 and PowerShell Core 7.0.0-preview.2) leading to the unexpected mix of remote and local information - see GitHub issue #10341.
As an aside: Similarly, PowerShell's ETS (extended type system) can introduce problems too, as shown in Mathias' answer here.
The best workaround is to simply use Get-ItemProperty (without a -Name argument) instead of Get-Item.
Mathias R. Jessen has provided the crucial pointer in a comment on the question, and js2010's answer provides a limited workaround and a pointer to the root cause, but it's worth providing more background information:
PowerShell comes with formatting instructions for type Microsoft.Win32.RegistryKey, as output by Get-Item with a registry path.
These formatting instructions define a calculated column named Property for the default (tabular) view, which helpfully shows a summary of the output registry key's values, which involves accessing the registry again, using Get-ItemProperty as shown in js2010's answer.
However, that behind-the-scenes Get-ItemProperty call always accesses the local registry - even when the keys were retrieved from a different machine, via PowerShell remoting, so you'll end up with a spurious mix of remote and local information.
Note that, technically, when Get-Item is run remotely, what you receive locally is an approximation of the original Microsoft.Win32.RegistryKey object, due to the serialization and deserialization involved in remoting. This approximation is a custom object with static copies of the original object's property values, and its (simulated) type name is Deserialized.Microsoft.Win32.RegistryKey - note the prefix.
PowerShell applies formatting instructions based on the full type name of output objects, but in the absence of a specific instructions or a given Deserialized.<originalTypeName> type, PowerShell applies the instructions for <originalTypeName>, which is what causes the problems here.
A - cumbersome, but edition-agnostic[1] - way to see the problematic formatting instruction is to run the following command:
(Get-FormatData Microsoft.Win32.RegistryKey -PowerShellVersion $PSVersionTable.PSVersion).FormatViewDefinition.Control | % {
$colNames = $_.Headers.Label
$colValues = $_.Rows.Columns.DisplayEntry.Value
foreach ($i in 0..($colNames.Count-1)) {
[pscustomobject] #{
ColumnName = $colNames[$i]
ColumnValue = $colValues[$i]
}
}
} | Format-Table -Wrap
This yields the column names and definitions for the table view:
ColumnName ColumnValue
---------- -----------
Name PSChildName
Property
$result = (Get-ItemProperty -LiteralPath $_.PSPath |
Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider |
Format-List | Out-String | Sort).Trim()
$result = $result.Substring(0, [Math]::Min($result.Length, 5000) )
if($result.Length -eq 5000) { $result += "..." }
$result
The workaround suggested in js2010's answer - piping to Format-Table * or Format-List * is effective in the sense that it prevents the inapplicable local information from being displayed: by specifying properties explicitly (even by wildcard pattern *), only those properties are displayed on output - not also the flawed calculated column.
However, while the true Property property of the output objects provides access to the value names in the registry key at hand, it doesn't provide the actual data, the way that the calculated Property column does.
By contrast, using Get-ItemProperty without a -Name argument in lieu of Get-Item as a workaround returns both value names and data (correctly even when remoting) and even does so without restrictions (whereas Get-Item limits output to 5000 chars.)
The output format will be slightly different, but all the information is there.
[1] That is, the command works also in PowerShell Core, where the built-in formatting instructions are no longer maintained as external *.format.ps1xl files and are instead compiled into the executable.
Pipe it to fl * or ft * so it doesn't use the format file to display the registry keys. The format file runs get-itemproperty locally to try to display the properties.
From the bottom of $PSHOME\Registry.format.ps1xml for type Microsoft.Win32.RegistryKey:
<ScriptBlock>
$result = (Get-ItemProperty -LiteralPath $_.PSPath |
Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider |
Format-List | Out-String | Sort).Trim()
$result = $result.Substring(0, [Math]::Min($result.Length, 5000) )
if($result.Length -eq 5000) { $result += "..." }
$result
</ScriptBlock>

adding variable to format list output

I have a question about adding a variable to the output of format-list. When I run the command:
get-CsAdUser | Format-List DisplayName, Enabled
I get the output
DisplayName : user01
Enabled : True
DisplayName : user02
Enabled : False
I have a variable $var1 = "folder one" that I need to add to the output so it looks like :
DisplayName : user01
Enabled : True
folder one
DisplayName : user02
Enabled : False
folder one
Anyone have an idea on how to do this? Thanks
On a meta note:
PetSerAl, as he often does, has provided an effective answer in a comment.
However, it is preferable to have an actual answer post that can be marked as accepted so as to signal to future readers what solution truly solved the OP's problem.
As PetSerAl notes:
get-CsAdUser | Format-List DisplayName, Enabled, #{Label = 'Folder'; Expression = {$var1}}
adds a third property to each input object's output that prints the value of variable $var1 as an ad-hoc, calculated property named Folder, following the preexisting DisplayName and Enabled properties.
The #{ Label = ...; Expression = ... } part of the command is a PowerShell hashtable literal that is an instance of a calculated property, which you can use with Select-Object, Format-Table, and Format-List, as described in this answer of mine.

PowerShell: ConvertFrom-Json to export multiple objects to csv

As you probably understand from the title, I'm new to PowerShell, having a hard time even trying to describe my question. So please forgive my terminology.
Scenario
I am using PowerShell to query the audit log of Office 365. The cmdlet Search-UnifiedAuditLog returns "multiple sets of objects"(?), one of which has an array of other objects(?). The output is JSON if I got this right.
Here is an example of what is returned (I will call it one "Set of Objects"):
RunspaceId : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RecordType : AzureActiveDirectoryStsLogon
CreationDate : 21/02/2017 12:05:23
UserIds : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Operations : UserLoggedIn
AuditData : {"CreationTime":"2017-02-21T12:05:23","Id":"{"ID":"00000000000000000","Type":3}],"ActorContextId":"xxxxxxxxxxxxxxxxxxxxxxxxx","ActorIpAddress":"xxxxxxxxxxxxx","InterSystemsId":"xxxxxxxxxxxxxxxxx","IntraSystemId":"000000000000-000000-000","Target":[{"ID":"00-0000-0000-c000-000000000000","Type":0}],"TargetContextId":"xxxxxxxxxxxxxxxxxx","ApplicationId":"xxxxxxxxxxxxxxxxxxxxxxxxxx"}
ResultIndex : 1
ResultCount : 16
Identity : xxxxxxxxxxxxxxxxxxxxxxxxxxx
IsValid : True
ObjectState : Unchanged
Now, I want some of the content of the AuditData line exported to a csv (normally containing much more data than copied here). This works fine for one "set of objects" (like the one above). To do this I use:
$LogOutput = Search-UnifiedAuditLog -StartDate 2/20/2017 -EndDate 2/23/2017 -ResultSize 1
$ConvertedOutput = ConvertFrom-Json -InputObject $LogOutput.AuditData
$ConvertedOutput | Select-Object CreationTime,UserId,ClientIP | Export-Csv -Path "C:\users\some.user\desktop\users.csv
ResultSize returns 1 instead of multiple "sets of objects". The ConvertFrom-Json does not work if I remove ResultSize.
So the question is:
Can I loop through all the "set of objects" and convert from json and have this exported line-by-line on a csv? Resulting in something like this:
UserId,Activity,UserIP
this#user.com, loggedIn, 10.10.10.10
that#user.org, accessedFile, 11.11.11.11
A pedagogic answer would be very, very much appreciated. Many thanks!
Instead of -ResultSize, try using Search-UnifiedAuditLog <args> | Select-Object -ExpandProperty AuditData | ConvertFrom-Json
This will make only the AuditData property get forwarded into ConvertFrom-Json and ignore the rest of the object from Search-UnifiedAuditLog

Powershell: Filtering server list by the 2nd octet of the IP address

I am trying to go through a list of servers for further querying via WMI. Unfortunately, if the scripts hits a server that cannot connect via WMI, it takes a long time before timing out.
These are generally servers in our DMZ, and allocated a specific address in the 2nd octet of the IP address, .92 for example. So I am looking to filter out these servers as the first step in my query, so it can be ignore in any further WMI queries.
I have found many examples on how to do this, and again, I cannot use WMI methods as this defeats the object (eg: Get-WmiObject Win32_NetworkAdapterConfiguration).
In the example below, the server "SERVER" has an IP of 192.9.4.1 and I want to filter out all servers with ".9" in the second octet. so I use a simple Test-Connection cmdlet, and aim to split the result. Before I can split it, the result of the Test-Connection is:
$IP4Address = Test-Connection _Computer SERVER -Count 1 | Select IPV4Address
#{IPV4Address=192.9.4.1}
Which means that I need to count 19 chars from the beginning to get my "9".
IPV4Address
$Octet = $IP4Address -split ("")
If ($Octet[19] -eq '9')
{write-host "In DMZ"}
Else
{write-host "Not in DMZ"}
Before you ask, I did try -split (".") but this doesn't seem to take any effect.
So why does the result come out like #{IPV4Address=192.9.4.1}? Is there a better solution?
The -split operator takes a regex pattern, not a character literal.
In regex, . is a wildcard meaning "any one character", resulting in a collection of empty strings (the positions between each character in the input string).
To match a literal dot, escape it with \
PS C:\> '10.0.122.12' -split '\.'
10
0
122
12
You don't need to split the IP Address, just use -match:
if ($IP4Address -match '^\d+\.9\.')
{
write-host "In DMZ"
}
else
{
write-host "Not in DMZ"
}
Regex:
^\d+\.9\.'
Your receive the output #{IPV4Address=192.9.4.1} because you are selecting the object. If you just want to get the string, use the -ExpandProperty parameter:
$IP4Address = Test-Connection _Computer SERVER -Count 1 | Select -ExpandProperty IPV4Address
Just wanted to show you another approach that is not split based as well as point out the issue you are having with the "ipaddress" object you have. What you are seeing in your variable is an object with an ipaddress property as supposed to a string which is what you are looking for. You need to expand that property or call the property from your object $IP4Address.Ipaddress. The former is the easier solution for single properties.
You can cast the result as the type accelerator [ipaddress]. This was you can call the method and return the octet you are look to filter on.
$IP4Address = Test-Connection -Computer SERVER -Count 1 | Select -ExpandProperty IPV4Address
([ipaddress]$IP4Address).GetAddressBytes()[1]
You would need to be careful with this approach as you should also be doing some checking to be sure that $IP4Address is not null.
if($IP4Address){([ipaddress]$IP4Address).GetAddressBytes()[1]}
To only get the value of the IPV4Address property you will have to expand it:
$IP4Address = Test-Connection -Computer SERVER -Count 1 |
Select-Object -ExpandProperty IPV4Address
Then you can use the .Split() method:
if($IPV4Address.Split(".")[1] -eq "9") {
"In DMZ"
} else {
"Not in DMZ"
}
PS : I usually use -Count 2 rather than -Count 1