Passing custom object array to a function - powershell

I'm trying to pass an array of custom objects to a function for further processing of these objects.
Here's the function where I create my custom object array:
Function GetNetworkAdapterList
{
# Get a list of available Adapters
$hnet = New-Object -ComObject HNetCfg.HNetShare
$netAdapters = #()
foreach ($i in $hnet.EnumEveryConnection)
{
$netconprop = $hnet.NetConnectionProps($i)
$inetconf = $hnet.INetSharingConfigurationForINetConnection($i)
$netAdapters += New-Object PsObject -Property #{
Index = $index
Guid = $netconprop.Guid
Name = $netconprop.Name
DeviceName = $netconprop.DeviceName
Status = $netconprop.Status
MediaType = $netconprop.MediaType
Characteristics = $netconprop.Characteristics
SharingEnabled = $inetconf.SharingEnabled
SharingConnectionType = $inetconf.SharingConnectionType
InternetFirewallEnabled = $inetconf.InternetFirewallEnabled
SharingConfigurationObject = $inetconf
}
$index++
}
return $netAdapters
}
Then in my main code I call above function like this:
$netAdapterList = GetNetworkAdapterList
The $netAdapterList returns the expected data, and I can do stuff like:
$netAdapterList | fl Name, DeviceName, Guid, SharingEnabled
So far so good.
Now I want to call a function passing in the $netAdapterList
I've created a dummy function like this:
Function ShowAdapters($netAdapterListParam)
{
$netAdapterListParam | fl Name, DeviceName, Guid, SharingEnabled
}
And when I invoke it like this:
ShowAdapters $netAdapterList
Nothing gets printed out.
I've tried changing the function's signature but still no luck:
Function ShowAdapters([Object[]]$netAdapterListParam)
Function ShowAdapters([Object]$netAdapterListParam)
Function ShowAdapters([PSObject[]]$netAdapterListParam)
Function ShowAdapters([array]$netAdapterListParam)
Anybody knows what I'm doing wrong? How can I get to my custom objects inside the function?

Thanks for your reply #Christian. Tried your steps, copy pasting bits and pieces into the shell and it indeed worked. However If i run the full .ps1 script is not printing out anything.
I've run the script in Powershell IDE setting breakpoints inside the ShowAdapters function, and $netAdapterListParam has indeed the expected custom objects array I'm passing in, so I've narrowed down the issue to be in the FL commandlet.
For some reason $netAdapterList | fl Name, DeviceName, Guid, SharingEnabled did not work for me, so I ended up using the following instead:
$formatted = $netAdapterListParam | fl Name, DeviceName, Guid, SharingEnabled | Out-String
Write-Host $formatted
That did the trick and the 4 properties were printed on the screen.
Lessons learned:
1) The Powershell IDE built into Win7 can be a very useful tool for debugging scripts
2) Format-List can be quirky when formatting custom objects, so Out-String was required.

Related

Convert Single Line to Multiple Lines

I am new to this Powershell.
I am trying to learn how to modified output.
When I run "Write-output $result | format-list" I have the following output
userDetails : #{id=AA:BB:CC:DD:11:22; connectionStatus=CONNECTED; hostType=WIRELESS;
authType=WPA2/WPA3+802.1x/FT-802.1x}
connectedDevice : {#{deviceDetails=}}
How do I rewrite this output to below using powershell 7.2 ? I would like to have
userDetails :
connectionStatus= CONNECTED
hostType = WIRELESS
authType = WPA2/WPA3+802.1x/FT-802.1x
connectedDevice :
Thank you for your help.
Note: I'm assuming that you're looking for a friendlier display representation of your data. For programmatic processing, Format-* cmdlets should be avoided, for the reasons explained in this answer.
What you're looking for is for Format-List to work recursively, i.e. to not only list the individual properties and their values for each input object itself, but also for nested objects contained in property values.
Format-List does not support this:
Nested objects are represented by their single-line .ToString() representations.
If they're part of a collection (enumerable), the individual elements' representations are joined with , on a single line, and are enclosed in {...}(!) as a whole. How many elements are shown at most is controlled by the $FormatEnumerationLimit preference variable, which defaults to 4.
However, you can approximate recursive listing behavior with Format-Custom; using a simplified example:
# Nested sample object to format.
[pscustomobject]#{
userDetails = [pscustomobject] #{
id = 'AA:BB:CC:DD:11:22'
connectionStatus= 'CONNECTED'
hostType = 'WIRELESS'
authType = 'WPA2/WPA3+802.1x/FT-802.1x'
}
connectedDevice = '...'
} |
Format-Custom -Depth 1 # use higher -Depth levels for multi-level expansion
Output:
class PSCustomObject
{
userDetails =
[
class PSCustomObject
{
id = AA:BB:CC:DD:11:22
connectionStatus = CONNECTED
hostType = WIRELESS
authType = WPA2/WPA3+802.1x/FT-802.1x
}
]
connectedDevice = ...
}
Note:
Caveat: If a custom view happens to be defined for a given input object's type via associated formatting data, it is that custom view that Format-Custom will invoke, not the structural representation shown above; however, this is rare ([datetime] is a rare example).
Apart from the output showing the structure recursively, the format differs from that of Format-List as follows:
Complex objects are enclosed in class <typeName> { ... }
Elements of collections (enumerables) each render on their own (group of) line(s), enclosed in [ ... ] overall. However, as with Format-List, the number of elements that are shown at most is limited by $FormatEnumerationLimit.
To prevent excessively nested output, Format-Custom stops recursing at a depth of 5 by default; you can control the recursion depth via the -Depth parameter, 1 meaning that only objects in immediate child properties are expanded.
When the recursion depth limit is reached, non-collection objects are represented by their .ToString() representations, as with Format-List.
Here is some code that produces output close to your desired output:
# Create sample data
$result = [pscustomobject] #{
userDetails = [pscustomobject]#{ id="AA:BB:CC:DD:11:22"; connectionStatus="CONNECTED"; hostType="WIRELESS"; authType="WPA2/WPA3+802.1x/FT-802.1x"}
connectedDevice = [pscustomobject]#{ deviceDetails=$null }
}
# Produce output
"userDetails :"
($result.userDetails |
Format-List -Property connectionStatus, hostType, authType |
Out-String).Trim() -replace '(?m)(?<=^[^:]+):', '='
"`nconnectedDevice :"
# TODO: add similar code as for .userDetails
Output:
userDetails :
connectionStatus = CONNECTED
hostType = WIRELESS
authType = WPA2/WPA3+802.1x/FT-802.1x
connectedDevice :
Using member access .userDetails to select a child object (similar to Select-Object -ExpandProperty userDetails).
Using Format-List -Property to output a list of the given properties
Using Out-String to create a string from the formatting data that is produced by Format-List. This string looks exactly like the output you normally see on the console.
Use String method .Trim() to remove whitespace (in this case newlines) from the beginning and end.
Use the -replace operator to replace the first : of each line by =. See this regex101 demo for more information.

Getting data out of structure not working

I'm trying to get the data out of the data structure, and I can see it's in there, but the variable the info is assigned to is null.
Let me know if you need more info for the method that assigns $SizeBasedVideoStringDevice, but it looks like this:
$SizeBasedVideoStringDevice:
[Object[7]]:
[0]:#{Key=LOAD_MEDIA;VALUE=SYSTEM.OBJECT[]}
[1]:#{KEY=LOAD_MEDIAB;VALUE=}
[2]:#{KEY=LOAD_MEDIAC;VALUE=}
[3]:#{KEY=LOAD_MEDIAD;VALUE=SYSTEM.OBJECT[]}
...
So some of them have a Value, which is fine.
When I look closer at that first value, it looks like this:
Value: [Object[5]]
[0]: "LoadDialog.MediaA"
[1]: "LoadDialog.MediaB"
[2]: "LoadDialog.MediaC"
...
I have the following code, and for some reason it's not finding that first one. Once I get the data out, I will loop thru and process each Value.
$tmp = "LOAD_MEDIA" #this will match first in data structure
$sizeBasedVideoString = $SizeBasedVideoStringDevice.where{$_Key -eq $tmp}.Value ###this is null
if($null -ne $sizeBasedVideoString)
{
$resultDPS_sized = New-Object System.Collections.Generic.List[string]
foreach($vid in $sizeBasedVideoString)
{
#get vid location
$temp = ProcessDPSMDB -mdbLookupString $vid -mdbFilePath $dpsMdbPathFull
$resultDPS_sized.Add($temp)
}
}
This is using PowerShell 5.1 and VSCode.

Accessing array outside of a PS function

I am having a hard time figuring out how to get the PSCustomObject/array out of the function. I have tried using $Global:ZipList as well as just passing the variables into an array directly w/o a custom object but no luck. The reason I need this, is I need to then loop through the array/list after I get the filenames and then was going to loop through this list and unzip each file and log it and process it based on the extension in the zip; this is to be used for multiple zips, so I can't predetermine the file extensions without grabbing the filenames in the zip into a list. I would just use a shell however some of the zips are password protected, haven't figured out how to pass a password scripted to the shell com unzip windows feature so stuck with 7z for now. Any help would be greatly appreciated! Thanks
Function ReadZipFile([string]$ZipFileName)
{
[string[]]$ReadZipFile = & 'C:\Program Files\7-Zip\7z.exe' l "$ZipFileName"
[bool]$separatorFound = $false
#$ZipList = #()
$ReadZipFile | ForEach-Object{
if ($_.StartsWith("------------------- ----- ------------ ------------"))
{
if ($separatorFound)
{
BREAK # Second separator; We're done!
}
$separatorFound = -not $separatorFound
}
else
{
if ($separatorFound)
{
[DateTime]$FileCreatedDate = [DateTime]::ParseExact($_.Substring(0, 19),"yyyy'-'MM'-'dd HH':'mm':'ss", [CultureInfo]::InvariantCulture)
[Int]$FileSize = [Int]"0$($_.Substring(26, 12).Trim())"
$ZipFileName = $_.Substring(53).TrimEnd()
$ZipList = [PSCustomObject] #{
ZipFileName=$ZipFileName
FileCreatedDate=$FileCreatedDate
FileSize=$FileSize}
}
}
}
}
$z = ReadZipFile $ZipFileName
$ZipList | Select-Object ZipFileName
To be able to select from array created in the function outside of it. I believe my if statements may be blocking the global variable feature when i tried using global:

How to avoid errors when casting null REST data to datetime

I am using a REST API to pull invoice data, from an external company, into an Excel file using PowerShell. I can pull the data just fine using the Invoke-RestMethod. Although, when it pulls the datetime fields, it is in a format I do not prefer. To automate my job as much as possible, is there a way to cast the data to datetime. Keep in mind, not every field has data so there are null fields which cause the errors. I basically want to pull all data, if it has an ugly-formatted datetime, change it to the way I want to see it, or if it is empty, just continue on.
I have tried [nullable[datetime]] (found it on google somewhere), but that throws other errors. I have tried using an 'IF' but that also generates in out set of syntax errors.
$out = Invoke-RestMethod -Uri $assetsURL
#$out.Invoices | Export-Excel $outputcsv
$out.Invoices |
Select-Object * |
ForEach-Object {
$Properties = [ordered]#{
InvoiceNumber = $_.InvoiceNumber
DueDate = $_.DueDate #[datetime]::parseexact($_.DueDate, 'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
Currency = $_.Currency
TotalDue = $_.TotalDue
PeriodFrom = $_.PeriodFrom #[datetime]::parseexact($_.PeriodFrom,'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
PeriodTo = $_.PeriodTo #[datetime]::parseexact($_.PeriodTo,'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
BillingName = $_.BillingName
BillingAddress = $_.BillingAddress
BillingAttentionTo = $_.BillingAttentionTo
BillingCity = $_.BillingCity
BillingState = $_.BillingState
BillingPostalCode = $_.BillingPostalCode
RemitToName = $_.RemitToName
RemitToAddress = $_.RemitToAddress
RemitToAttentionTo = $_.RemitToAttentionTo
RemitToCity = $_.RemitToCity
RemitToState = $_.RemitToState
RemitToPostalCode = $_.RemitToPostalCode
Lease = $_.Lease
Schedule = $_.Schedule
CreatedDate = $_.CreatedDate #[datetime]::parseexact($_.CreatedDate, 'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
}
New-Object psobject -Property $Properties
} | Export-Excel $outputcsv
Just updating because I may not have been clear on what the issue is. In the columns 'DueDate', 'PeriodFrom', 'PeriodTo', and 'CreatedDate', these are all pulled from the REST API. 'DueDate' and 'CreatedDate' seem to be properly populated but 'PeriodFrom' and 'PeriodTo' may or may not have a date.
Currently, without any trickery, the date (if it exists) returns as:
2019-09-05T00:00:00
I would like to return an empty field if it does not exist or return the date in the like the following if it does exit:
2019-09-05
You can use TryParseExact:
ForEach-Object {
$parsedDate = [datetime]::MinValue
$Properties = [ordered]#{ # remove the CreatedDate from the HashTable
[...]
if ([datetime]::TryParseExact($_.CreatedDate,'yyyy-MM-ddThh:mm:ss',
[System.Globalization.CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::None,[ref]$ParsedDate))
{$Properties.Add("CreatedDate", $parsedDate.ToString("yyyy-MM-dd"))}
}
else {
$Properties.Add("CreatedDate",$null)
}
Note that you can add array of string formats instead of a single string, for example:
[String[]]$DateStrings = "M/dd/yyyy h:mmtt","M/d/yyyy h:mmtt","MM/d/yyyy h:mmtt"
Then it would be:
[datetime]::TryParseExact($_.CreatedDate,$DateStrings...)

Parse powershell script parameters

Is there an easy way to parse the params from a powershell script file
param(
[string]$name,
[string]$template
)
I have started reading the file and wondered if there is a better way, maybe by a help/man command?
class PowerShellParameter {
public string Name;
public string Type;
public string Default;
}
string[] lines = File.ReadAllLines(path);
bool inparamblock = false;
for (int i = 0; i < lines.Length; i++) {
if (lines[i].Contains("param")) {
inparamblock = true;
} else if (inparamblock) {
new PowerShellParameter(...)
if (lines[i].Contains(")")) {
break;
}
}
}
There are at least two possibilies. First one (imho better): use Get-Command:
# my test file
#'
param(
$p1,
$p2
)
write-host $p1 $p2
'# | Set-content -path $env:temp\sotest.ps1
(Get-Command $env:temp\sotest.ps1).parameters.keys
For all members look at
Get-Command $env:temp\sotest.ps1 | gm
#or
Get-Command $env:temp\sotest.ps1 | fl *
The other (harder way) is to use regular expression
[regex]::Matches((Get-Help $env:temp\sotest.ps1), '(?<=\[\[-)[\w]+') | select -exp Value
I like the solution with Get-Command proposed by #stej. Unfortunately it does not work if script parameters have explicit types specified and an assembly of such a type is not yet loaded into the session. That is why I still use this script: Get names of script parameters
I'm not really sure what you're after, is it documenting your scripts? In that case have a look at Get-Help about_Comment_Based_Help. It will tell you how to do that, and after that you can use Get-Help on your script/module.
If you're after more strict parameter handling, take a look at about_functions_advanced_parameters and about_functions_cmdletbindings on how to better structure parameters. For example,
[Parameter(Position=0,Mandatory=$true,HelpMessage='Enter
architecture("OSX","WinXP","Win7","Linux")')]
[ValidateSet("OSX","WinXP","Win7","Linux")]
[string]$architecture
will make that parameter mandatory, read from position 0 of the command, allow only a value from the given set, and give a brief help message when asking for input if that parameter was not given.