Powershell Foreach not skipping values from array - powershell

I am trying to write a script that downloads web sites information. I am able to download the information but I cannot seem to get the filtering working. I have an a series of values that I want skipped stored in $TakeOut but it does not recognize the values in the if -eq $TakeOut. I have to write a line for each value.
What I am wondering is, if there is a way to use a $value as over time there will be a considerable amount of values to skip.
This works but is not practical in the long run.
if ($R.innerText -eq "Home") {Continue}
Something like this would be preferable.
if ($R.innerText -eq $TakeOut) {Continue}
Here is a sample of my code.
#List of values to skip
$TakeOut = #()
$TakeOut = (
"Help",
"Home",
"News",
"Sports",
"Terms of use",
"Travel",
"Video",
"Weather"
)
#Retrieve website information
$Results = ((Invoke-WebRequest -Uri "https://www.msn.com/en-ca/").Links)
#Filter and format to new table of values
$objects = #()
foreach($R in $Results) {
if ($R.innerText -eq $TakeOut) {Continue}
$objects += New-Object -Type PSObject -Prop #{'InnerText'= $R.InnerText;'href'=$R.href;'Title'=$R.href.split('/')[4]}
}
#output to file
$objects | ConvertTo-HTML -As Table -Fragment | Out-String >> $list_F

You cannot meaningfully use an array as the RHS of an -eq operation (the array will be implicitly stringified, which won't work as intended).
PowerShell has operators -contains and -in to test membership of a value in an array (using -eq on a per-element basis - see this answer for background); therefore:
if ($R.innerText -in $TakeOut) {Continue}
Generally, your code can be streamlined (PSv3+ syntax):
$TakeOut =
"Help",
"Home",
"News",
"Sports",
"Terms of use",
"Travel",
"Video",
"Weather"
#Retrieve website information
$Results = (Invoke-WebRequest -Uri "https://www.msn.com/en-ca/").Links
#Filter and format to new table of values
$objects = foreach($R in $Results) {
if ($R.innerText -in $TakeOut) {Continue}
[pscustomobject #{
InnerText = $R.InnerText
href = $R.href
Title = $R.href.split('/')[4]
}
}
#output to file
$objects | ConvertTo-HTML -As Table -Fragment >> $list_F
Note the absence of #(...), which is never needed for array literals.
Building an array in a loop with += is slow (and verbose); simply use the foreach statement as an expression, which returns the loop body's outputs as an array.
[pscustomobject] #{ ... } is PSv3+ syntactic sugar for constructing custom objects; in addition to being faster than a New-Object call, it has the added advantage of preserving property order.
You could write the whole thing as a single pipeline:
#Retrieve website information
(Invoke-WebRequest -Uri "https://www.msn.com/en-ca/").Links | ForEach-Object {
#Filter and format to new table of values
if ($_.innerText -in $TakeOut) {return}
[pscustomobject #{
InnerText = $_.InnerText
href = $_.href
Title = $_.href.split('/')[4]
}
} | ConvertTo-HTML -As Table -Fragment >> $list_F
Note the need to use return instead of continue to move on to the next input.

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 to fix System Object value in PowerShell

I'm Importing a CSV file and reading a column that look like this
Exchange Mailboxes
Include:[john.doe#outlook.com]
Include:[david.smith#outlook.com]
Include:[kevin.love#outlook.com]
I use Get-EXOMailbox to get their DisplayName and Id. After that I'm trying to pass it in my New-Object like below so that I can export it. The problem I have is when I look at my Excel file, it showing System.Object[] on every row instead of showing each actual DisplayName and Id.
Any help on how to display it correctly would be really appreciated.
$result = Import-Csv "C:\AuditLogSearch\Dis\Modified-Audit-Log-Records.csv" |
Where-Object { -join $_.psobject.Properties.Value } |
ForEach-Object {
$exoMailbox = ($_.'Exchange Mailboxes' -split '[][]')[1]
$exoUser = Get-EXOMailbox -Filter "PrimarySmtpAddress -eq '$exoMailbox'"
# Construct and output a custom object with the properties of interest.
[pscustomobject] #{
UserName = $exoUser.DisplayName
UserId = $exoUser.Identity
}
}
New-Object PsObject -Property #{
'Searched User' = $result.UserName //I'm trying to pass here
'SharePoint URL' = $spUrl
'Searched User GMID' = $result.UserId //and here
'Site Owner' = $spositeOwner
User = $u.User
"Result Status" = $u."Result Status"
"Date & Time" = $u."Date & Time"
"Search Conditions" = $u."Search Conditions"
"SharePoint Sites" = $u."SharePoint Sites"
"Exchange Public Folders" = $u."Exchange Public Folders"
"Exchange Mailboxes" = $u."Exchange Mailboxes".Split([char[]]#('[', ']'))[1]
"Case Name" = $u."Case Name"
"Search Criteria" = $u."Search Criteria"
"Record Type" = $u."Record Type"
"Hold Name" = $u."Hold Name".Split(('\'))[1]
"Activity" = if ($null -ne ($importData | where-object { $_.Name -eq $u."Activity" }).Value) { ($importData | where-object { $_.Name -eq $u."Activity" }).Value }
else { $u."Activity" }
} | Select-object -Property User, "Date & Time", "Case Name", "Hold Name", "Record Type", "Activity" , "Searched User", "Searched User GMID", "SharePoint URL", "Exchange Mailboxes", "Exchange Public Folders" , "Search Criteria", "Result Status"
}
$xlsx = $result | Export-Excel #params
$ws = $xlsx.Workbook.Worksheets[$params.Worksheetname]
$ws.Dimension.Columns
$ws.Column(1).Width = 20
$ws.Column(2).Width = 20
$ws.Column(3).Width = 15
$ws.Column(4).Width = 15
$ws.Column(5).Width = 15
$ws.Column(6).Width = 160
$ws.View.ShowGridLines = $false
Close-ExcelPackage $xlsx
$result is an array of objects, containing an object for each non-empty row in your input CSV; thus, adding values such as $result.UserName to the properties of the object you're creating with New-Object will be arrays too, which explains your symptom (it seems that Export-Excel, like Export-Csv doesn't meaningfully support array-valued properties and simply uses their type name, System.Object[] during export).
It sounds like the easiest solution is to add the additional properties directly in the ForEach-Object call, to the individual objects being constructed and output via the existing [pscustomobject] literal ([pscustomobject] #{ ... }):
$result =
Import-Csv "C:\AuditLogSearch\Dis\Modified-Audit-Log-Records.csv" |
Where-Object { -join $_.psobject.Properties.Value } | # only non-empty rows
ForEach-Object {
$exoMailbox = ($_.'Exchange Mailboxes' -split '[][]')[1]
$exoUser = Get-EXOMailbox -Filter "PrimarySmtpAddress -eq '$exoMailbox'"
# Construct and output a custom object with the properties of interest.
[pscustomobject] #{
UserName = $exoUser.DisplayName
UserId = $exoUser.Identity
# === Add the additional properties here:
'Searched User' = $exoUser.UserName
'SharePoint URL' = $spUrl
'Searched User GMID' = $exoUser.UserId
'Site Owner' = $spositeOwner
# ...
}
}
Note:
The above shows only some of the properties from your question; add as needed (it is unclear where $u comes from in some of them.
Using a custom-object literal ([pscustomobject] #{ ... }) is not only easier and more efficient than a New-Object PSObject -Property #{ ... }[1] call, unlike the latter it implicitly preserves the definition order of the properties, so that there's no need for an additional Select-Object call that ensures the desired ordering of the properties.
[1] Perhaps surprisingly, PSObject ([psobject]) and PSCustomObject ([pscustomobject]) refer to the same type, namely System.Management.Automation.PSObject, despite the existence of a separate System.Management.Automation.PSCustomObject, which custom-objects instances self-report as (([pscustomobject] #{}).GetType().FullName) - see GitHub issue #4344 for background information.

Looping through and appending a hastable instead of adding to keys

I'm looping through a CSV file and using ForEach-Object loop to grab info to attempt to update in_stock status on Woocommerce, what ends up happening is the woocommerce only see's one entry. I'm not a programmer, I'm still learning PowerShell and for the life of me I just can't understand the logic of for loops and it's output properly. I know it reads the entries in the CSV, but I think it's just overwriting the previous entry.
Another issue I'm having is properly setting in_stock values as true and false for each object respectively, if one is true then all false entries are also set as true. I can't seem to figure out how to assign true | false correctly.
I've been looking up PowerShell using the MS docs on it and how to append hashtables but I'm still not finding the answers or examples that will point me in the right direction. I've gone so far as to purchase PowerShell tutorials offsite and still haven't found a way to do this properly.
$website = "https://www.mywebsite.com"
$params += #{
type= #();
name = #();
SKU = #();
catalog_visibility = #();
regular_price = #();
in_stock = #();
categories = #();
}
$csv = Import-Csv C:\test\test-upload.csv
$csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog',
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer
reviews?', 'Regular price', Categories | ForEach-Object{
$params.type += $_.type
$params.SKU += $_.SKU
$params.name += $_.name
$params.catalog_visibility += $_.'Visibility in catalog'
$params.categories += $_.Categories
$params.regular_price += $_.'Regular price'
$params.in_stock += $_.'In stock?'
if ($params.in_stock = 0) {$params.in_stock -replace 0, $false}
elseif($params.in_stock = 1) {$params.in_stock -replace 1, $true}
}
foreach($key in $params.keys){
Write-Output $params[$key]
}
I'm looking to get something like this
{
"name": "part 1",
"type": "simple",
"SKU": "0001",
"regular_price": "21.99",
"in_stock: false",
"categories: category 1",
"catalog_visibility": "hidden",
},
{
"name": "part 2",
"type": "simple",
"SKU": "0002",
"regular_price": "11.99",
"in_stock: true",
"categories: category 2",
"catalog_visibility": "hidden",
}
and what I am actually getting is
{
"name": "part 1 part 2",
"type": "simple simple ",
"SKU": "0001 0002",
"regular_price": "21.99 11.99",
"in_stock: true true",
"categories: category 1 category 1",
"catalog_visibility": "hidden hidden",
}
I would really appreciate it if someone could point me in the right direction and give me a few tips on best practice
Since you're new to programming let's talk a little bit about arrays and hashtables.
Arrays are like lists (sometimes they are called lists too), specifically, ordered lists by position.
Hashtables are a type of dictionary, whereby you have a Key that corresponds to a Value.
In PowerShell the syntax you're using for creating an array is #() (that one's empty, it could contain items) and the syntax you use for creating a hashtable is #{} (also empty, could contain values).
You don't show your initial definition of $params, but based on the rest of the code I'm going to assume it's like this:
$params = #()
Then, you have this:
$params += #{
type= #();
name = #();
SKU = #();
catalog_visibility = #();
regular_price = #();
in_stock = #();
categories = #();
}
So what this would mean is that you took your array, $params, and added a new item to it. The new item is the hashtable literal you defined here. All the names you added, like type, name, SKU, etc. are Keys.
According to your desired output, it does look like you want an array of hashtables, so I think that part is correct.
But note that the values you assigned to them are all empty arrays. This is curious because what you showed as your desired output has each hashtable with those keys being singular values, so I think that's one issue, and in fact it's clouding the area where the problem really is.
So let's skip ahead to the body of the loop, where you use this pattern:
$params.type += $_.type
$params.SKU += $_.SKU
$params.name += $_.name
$params.catalog_visibility += $_.'Visibility in catalog'
$params.categories += $_.Categories
$params.regular_price += $_.'Regular price'
$params.in_stock += $_.'In stock?'
Remember that $params is an array, so you should have items in it starting at position 0, like $params[0], $params[1], etc. To change the SKU of the second hashtable in the array, you'd use $params[1].SKU or $params[1]['SKU'].
But what you're doing is just $params.SKU. In many languages, and indeed in PowerShell before v3, this would throw an error. The array itself doesn't have a property named SKU. In PowerShell v3 though the dot . operator was enhanced to allow it to introspect into an array and return each item's property with the given name, that is:
$a = #('apple','orange','pear')
$a.PadLeft(10,'~')
is the same as if we had done:
$a = #('apple','orange','pear')
$a | ForEach-Object { $_.PadLeft(10,'~') }
It's very useful but might be confusing you here.
So back to your object, $params is an array with, so far, only a single hashtable in it. And in your loop you aren't adding anything to $params.
Instead you ask for $params.SKU, which in this case will be the SKU of every hashtable in the array, but there's only one hashtable, so you only get one SKU.
Then you add to the SKU itself:
$params.SKU += $_.SKU
Here's the part where setting SKU initially to an empty array is hiding your issue. If SKU were a string, this would fail, because strings don't support +=, but since it's an array, you're taking this new value, and adding it to the array of SKUs that exist as the value of the single hashtable you're working against.
Where to go from here
don't use arrays for your values in this case
create a new hashtable in each iteration of your loop, then add that new hashtable to the $params array
Let's take a look:
$params = #()
$csv = Import-Csv C:\test\test-upload.csv
$csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog',
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer
reviews?', 'Regular price', Categories | ForEach-Object {
$params += #{ # new hashtable here
type = $_.type
SKU = $_.SKU
name = $_.name
catalog_visibility = $_.'Visibility in catalog'
categories = $_.Categories
regular_price = $_.'Regular price'
}
}
This is the main problem you have, I left out the in stock part because I'm going to explain that logic separately.
$params.in_stock = $_.'In stock?'
if ($params.in_stock = 0) {$params.in_stock -replace 0, $false}
elseif($params.in_stock = 1) {$params.in_stock -replace 1, $true}
}
It looks like your CSV has an In stock? column that can be 0 or 1 for false/true.
First thing I'll address is that = in PowerShell is always assignment. Testing for equality is -eq, so:
$params.in_stock = $_.'In stock?'
if ($params.in_stock -eq 0) {$params.in_stock -replace 0, $false}
elseif($params.in_stock -eq 1) {$params.in_stock -replace 1, $true}
}
Next, let's talk about true/false values; they're called Boolean or bool for short, and you should usually use this data type to represent them. Any time you do a comparison for example like $a -eq 5 you're returning a bool.
There's strong support for converting other types to bool, for instance if you want to evaluate a number as bool, 0 is false, and all other values are true. For strings, a $null value or an empty string is false, all other values are true. Note that if you have a string "0" that is true because the string has a value.
That also means that the number 0 is not the same as the string '0', but PowerShell does attempt to do conversions between types, usually trying to convert the right side's type to the left side for comparison, so PowerShell will tell you 0 -eq '0' is true (same with '0' -eq 0).
And for your situation, reading from a CSV, those values will end up as strings, but because of the above, your equality tests will work anyway (it's just worth knowing the details).
The issue with your use of -replace though, is that it's a string operation, so even if it works, you're going to end up with the string representation of a boolean, not the actual bool, even though you said to use $true and $false directly (and this is again because of type conversion; -replace needs a string there, PowerShell converts your bool to string to satisfy it).
So, after that long-winded explanation, what makes sense then is this:
$params.in_stock = $_.'In stock?'
if ($params.in_stock -eq 0) {
$params.in_stock = $false
} elseif($params.in_stock -eq 1) {
$params.in_stock -eq $true
}
in fact, the elseif isn't necessary since you can only have 2 values:
$params.in_stock = $_.'In stock?'
if ($params.in_stock -eq 0) {
$params.in_stock = $false
} else {
$params.in_stock -eq $true
}
Even further though, we can use conversions to not need a conditional at all. Remember what I said about converting strings to numbers, and numbers to bool.
0 -as [bool] # gives false
"0" -as [bool] # gives true (whoops)
"0" -as [int] # gives the number 0
"0" -as [int] -as [bool] # false!
Now, we can do this:
$params.in_stock = $_.'In stock?' -as [int] -as [bool]
cool! Let's put it back into the other code:
$params = #()
$csv = Import-Csv C:\test\test-upload.csv
$csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog',
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer
reviews?', 'Regular price', Categories | ForEach-Object {
$params += #{ # new hashtable here
type = $_.type
SKU = $_.SKU
name = $_.name
catalog_visibility = $_.'Visibility in catalog'
categories = $_.Categories
regular_price = $_.'Regular price'
in_stock = $_.'In stock?' -as [int] -as [bool]
}
}
Deeper dive!
Piping: you're doing some calls like the Import-Csv call and assigning its output to a variable, then piping that variable into another command. That's fine, it's not wrong, but you could also just pipe the first command's output directly into the second like so:
$params = #()
Import-Csv C:\test\test-upload.csv |
Select-Object -Property Type, SKU, Name, 'Visibility in catalog',
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer
reviews?', 'Regular price', Categories |
ForEach-Object {
$params += #{ # new hashtable here
type = $_.type
SKU = $_.SKU
name = $_.name
catalog_visibility = $_.'Visibility in catalog'
categories = $_.Categories
regular_price = $_.'Regular price'
in_stock = $_.'In stock?' -as [int] -as [bool]
}
}
I updated to formatting a little to show that you can use a line break after a pipe |, which can look a little cleaner.
About Select-Object: its purpose is to take objects with a certain set of properties, and give you back a new object with a more limited (or sometimes with brand new) properties (it has other uses around changing the number of objects or filtering the array in other ways that aren't relevant here at the moment).
But I bring this up, because all the properties (columns) you're selecting are by name, and therefore must exist on the input object. And since you refer to each one later directly as opposed to display the entire thing, there's no reason to use Select-Object to filter down the properties, so that entire call can be removed:
$params = #()
Import-Csv C:\test\test-upload.csv |
ForEach-Object {
$params += #{ # new hashtable here
type = $_.type
SKU = $_.SKU
name = $_.name
catalog_visibility = $_.'Visibility in catalog'
categories = $_.Categories
regular_price = $_.'Regular price'
in_stock = $_.'In stock?' -as [int] -as [bool]
}
}
Nice! Looking slim.
About arrays and +=. This is ok in most cases to be honest, but you should know that each time you do this, in reality a new array is being created and all of the original items plus the new item are being copied into it. This doesn't scale, but again it's fine in most use cases.
What you should also know is that the output from a pipeline (like any command, or your main script code, or the body of ForEach-Object is all sent to the next command in the pipeline (or back out the left side if there's nothing else). This can be any number of items, and you can use assignment to get all of those values, like:
$a = Get-ChildItem $env:HOME # get all of items in the directory
$a will be an array if there's more than one item, and during processing it doesn't continually create and destroy arrays.
So how is this relevant to you? It means you don't have to make $params an empty array and append to it, just return your new hashtables in each loop iteration, and then assign the output of your pipeline right to $params!
$params = Import-Csv C:\test\test-upload.csv |
ForEach-Object {
#{ # new hashtable here
type = $_.type
SKU = $_.SKU
name = $_.name
catalog_visibility = $_.'Visibility in catalog'
categories = $_.Categories
regular_price = $_.'Regular price'
in_stock = $_.'In stock?' -as [int] -as [bool]
} # output is implicit
}
And now we've got your script down to a single pipeline (you could make it a single line but I prefer multi-line formatting).
So what you are doing is a lot of += to try and create an array, but you're doing it at the wrong level. What you want to do is create a hashtable (or quite possibly a PSCustomObject) for each item in the CSV, and capture them as an array of objects (be they hashtable objects, or PSCustomObject objects). So, let's try and restructure things a little to do that. I'm ditching the template, we don't care, we're defining it for each object anyway. I'm going to output a hashtable for each item in the ForEach-Object loop, and capture it in $params. This should give you the results you want.
$website = "https://www.mywebsite.com"
$csv = Import-Csv C:\test\test-upload.csv
$params = $csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog', 'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer reviews?', 'Regular price', Categories | ForEach-Object{
#{
type = $_.type
SKU = $_.SKU
name = $_.name
catalog_visibility = $_.'Visibility in catalog'
categories = $_.Categories
regular_price = $_.'Regular price'
in_stock = [boolean][int]($_.'In stock?')
}
}

Sorting and Comparing Arraylists

I'm pretty new to Powershell and have a problem.
It seems I can get neither the Compare-Object nor the Sort-Object funtions to work.
I got two Arraylists which I fill with 10 objects of type "Table".
#Create Arraylists
$list1 = new-object System.Collections.ArrayList
$list2 = new-object System.Collections.ArrayList
#Declare Table-Class
class Table {
[String] $name
[Int] $number
}
#Fill list1
[Int] $i = 0
while ($i -lt 10) {
$entry = new-Object Table
$entry.name = "Name";
$entry.number = $i;
$list1.Add($entry)
$i++
}
#Fill list2
[Int] $j = 10
while ($j -gt 0) {
$entry = new-Object Table
$entry.name = "name";
$entry.number = $j;
$list2.Add($entry)
$j--
}
Now I want to compare these two ArrayLists like this:
Compare-Object $list1 $list2 | ForEach-Object {$_.InputObject}
This doesn't seem to work and I think it's because I'm not really doing anything with the comparison. If someone could help me with the correct Syntax I'd be really happy.
Anyway, I think this comparison would somehow return a $false boolean.
If that were true, I'd like to sort $list2 by $entry.number.
I'm attempting this like that:
$list2 = $list2 | Sort-Object -Property { $_[1] }
But my list doesn't change at all. I already tried several different "solutions" but it seems none of these is a real "solution".
I'd be really glad if someone could help me or at least point me in the right direction.
EDIT: For future readers, here is the working syntax:
#sort list by the property number
$list2 = $list2 | sort-object -Property number
#count differences and if differences greater than 0, do stuff
[Int] $diff
$properties = $list1 | Get-Member -MemberType Property | Select-Object -ExpandProperty Name
foreach ($property in $properties) {
$diff = 0
$diff = (Compare-Object $list1 $list2 -Property "$property").count
if ($diff -ne 0) {
#do stuff
}
else {
#do something else
}
}
Your sorting does not work because -Property expects you to pass the name of an actual property. In your case the class two properties: $name and $number. You can also check what properties an object has by using $list2 | Get-Member. So in order to sort, you can use:
$list2 = $list2 | Sort-Object -Property number
Now the reason Compare-Object is not working is because it's implemented differently that one might expect. See this answer explaining how it works.
One other thing you should keep in mind: Calling $list2.Add($entry) actually returns an integer (the index of the inserted element). PowerShell returns all uncaptured output. This can cause some unexpected behavior/output if you are not careful. So you should get in the habit of writing [void] $list2.Add($entry), unless you really want to return those indexes.

Powershell - Exporting Non-string Multivalued Properties

I have the following to dynamically expand properties of objects I feed into the function, but ToString() is often spitting out the property types instead:
Function ExpandMultivaluedProperties
{
Param(
[PSObject]$InputObject
)
$results= $InputObject |
ForEach-Object {
$properties = New-Object PSObject
$_.PSObject.Properties |
ForEach-Object {
$propertyName = $_.Name
$propertyValue = $_.Value
If ($propertyValue -NE $NULL) {
$values = #()
ForEach ($value In $propertyValue) {
$values += $value.ToString()
}
Add-Member -inputObject $properties NoteProperty -name $propertyName -value "$([String]::Join(";",$values))"
} Else {
Add-Member -inputObject $properties NoteProperty -name $propertyName -value $NULL
}
}
$properties
}
return $results
}
ExpandMultivaluedProperties -InputObject (Get-ExchangeCertificate) | Export-CSV -path "Cert.csv" -NoTypeInformation
In particular with the results of Get-ExchangeCertificate, what I end up with is
System.Security.Cryptography.X509Certificates.X500DistinguishedName
as the IssuerName for each certificate.
The code works fine for any string-friendly properties, but I'm aiming for it to be robust enough to handle any property that would normally show up correctly with a | Format-List
Any thoughts on how to print/expand similar properties programmatically without having to use a "Select" expression?
In a string context, the instances of many types default to simply printing their type name - e.g., System.Security.Cryptography.X509Certificates.X500DistinguishedName - which is often not helpful.
However, you can use Out-String instead, which applies PowerShell's default output formatting (the same output format you'd see in the console):
$values += Out-String -InputObject $value
One caveat is that the resulting strings often have leading or trailing empty lines. You can remove leading and/or trailing empty lines with the following variation:
$values += (Out-String -InputObject $value).Trim()
Caveats:
The default width of the output lines for non-string data is based on the PowerShell host, and defaults to the width of the window buffer in the regular console minus 1 (excluding the line break); i.e., 79 in with the default console window size.
Use -Width to specify an output line width (excluding the line break) explicitly.
Longer lines are truncated, i.e., the information is lost.
Note that [string] values are not affected - they are output as-is.
Conversely, if the output is in table format (implicit use of Format-Table), shorter lines are right-padded with spaces to the implied or specified width.
Given that lines can be padded, you shouldn't use something like Out-String -Width ([int]::MaxValue]) to prevent truncation (in fact, you may run out of memory).
Instead, use a reasonably high value such as -Width 255, as #Abraxas000 did in his own answer.
If you wanted to condense the typically multi-line output to a one-liner, use something like the the following, though :
$values += ($value | Out-String).Trim() -replace '\s*\r?\n', '; ' -replace '\s+', ' '
;  was chosen to replace the line breaks here, and line-internal runs of whitespace are normalized to a single space each - whether the results are still readable probably both depends on the specific output format and the eye of the beholder.
To give a concrete example:
> $ht = #{ one = 1; two = 2; three = 3 } # sample hashtable
> $val = "$ht"; "ht: $val" # hashtable is stringified -> type name only
ht: System.Collections.Hashtable
> $val = Out-String -InputObject $ht; "ht: $val" # Out-String creates meaningful representation
ht:
Name Value
---- -----
one 1
two 2
three 3
> ($ht | Out-String).Trim() -replace '\s*\r?\n', '; ' -replace '\s+', ' '
Name Value; ---- -----; one 1; two 2; three 3
#mklement0 Gave me an answer that lead me as close to ideal as I have time for, so go up-vote it!
My code as of posting this:
Function ExpandMultivaluedProperties
{
Param(
[PSObject]$InputObject
)
$results= $InputObject |
ForEach-Object {
$properties = New-Object PSObject
$_.PSObject.Properties |
ForEach-Object {
$propertyName = $_.Name
$propertyValue = $_.Value
If ($propertyValue -NE $NULL) {
$values = #()
ForEach ($value In $propertyValue) {
if (($value.ToString()).StartsWith("System."))
{
$values += (Out-String -InputObject $value -Width 255).Trim()
}
else
{
$values += $value.ToString()
}
}
Add-Member -inputObject $properties NoteProperty -name $propertyName -value "$([String]::Join(";",$values))"
} Else {
Add-Member -inputObject $properties NoteProperty -name $propertyName -value $NULL
}
}
$properties
}
return $results
}
This gives me the one-line value I expect to see for the simpler properties while properly expanding the complex properties.
Even if the property is simple and the value starts with "System.", the data will still be present, just not in the simpler, one-liner format I prefer for CSVs.
Thanks again, #mklement0!