How can I get these variables to output together? [duplicate] - powershell

I'm fairly new to PowerShell, and am wondering if someone knows of any better way to accomplish the following example problem.
I have an array of mappings from IP address to host-name. This represents a list of active DHCP leases:
PS H:\> $leases
IP Name
-- ----
192.168.1.1 Apple
192.168.1.2 Pear
192.168.1.3 Banana
192.168.1.99 FishyPC
I have another array of mappings from MAC address to IP address. This represents a list of IP reservations:
PS H:\> $reservations
IP MAC
-- ---
192.168.1.1 001D606839C2
192.168.1.2 00E018782BE1
192.168.1.3 0022192AF09C
192.168.1.4 0013D4352A0D
For convenience, I was able to produce a third array of mappings from MAC address to IP address and host name using the following code. The idea is that $reservations should get a third field, "Name", which is populated whenever there's a matching "IP" field:
$reservations = $reservations | foreach {
$res = $_
$match = $leases | where {$_.IP -eq $res.IP} | select -unique
if ($match -ne $NULL) {
"" | select #{n="IP";e={$res.IP}}, #{n="MAC";e={$res.MAC}}, #{n="Name";e={$match.Name}}
}
}
The desired output is something like this:
PS H:\> $ideal
IP MAC Name
-- --- ----
192.168.1.1 001D606839C2 Apple
192.168.1.2 00E018782BE1 Pear
192.168.1.3 0022192AF09C Banana
192.168.1.4 0013D4352A0D
Is there any better way of doing this?

After 1.5 years, the cmdlet I had pasted in the original answer has undergone so many updates that it has become completely outdated. Therefore I have replaced the code and the ReadMe with a link to the latest version.
Join-Object
Combines two object lists based on a related property between them.
Description
Combines properties from one or more objects. It creates a set that can be saved as a new object or used as it is. An object join is a means for combining properties from one (self-join) or more object lists by using values common to each.
Main features
Intuitive (SQL like) syntax
Smart property merging
Predefined join commands for updating, merging and specific join types
Well defined pipeline for the (left) input objects and output objects (preserves memory when correctly used)
Performs about 40% faster than Compare-Object on large object lists
Supports (custom) objects, data tables and dictionaries (e.g. hash tables) for input
Smart properties and calculated property expressions
Custom relation expressions
Easy installation (dot-sourcing)
Supports PowerShell for Windows (5.1) and PowerShell Core
The Join-Object cmdlet reveals the following proxy commands with their own (-JoinType and -Property) defaults:
InnerJoin-Object (Alias InnerJoin or Join), combines the related objects
LeftJoin-Object (Alias LeftJoin), combines the related objects and adds the rest of the left objects
RightJoin-Object (Alias RightJoin), combines the related objects and adds the rest of the right objects
FullJoin-Object (Alias FullJoin), combines the related objects and adds the rest of the left and right objects
CrossJoin-Object (Alias CrossJoin), combines each left object with each right object
Update-Object (Alias Update), updates the left object with the related right object
Merge-Object (Alias Merge), updates the left object with the related right object and adds the rest of the new (unrelated) right objects
ReadMe
The full ReadMe (and source code) is available from GitHub: https://github.com/iRon7/Join-Object
Installation
There are two versions of this Join-Object cmdlet (both versions supply the same functionality):
Join Module
Install-Module -Name JoinModule
Join Script
Install-Script -Name Join
(or rename the Join.psm1 module to a Join.ps1 script file)
and invoked the script by dot sourcing:
. .\Join.ps1
Answer
To answer the actual example in the question:
$reservations |LeftJoin $leases -On IP
IP MAC Name
-- --- ----
192.168.1.1 001D606839C2 Apple
192.168.1.2 00E018782BE1 Pear
192.168.1.3 0022192AF09C Banana
192.168.1.4 0013D4352A0D
Performance
A little word on performance measuring:
The PowerShell pipeline is designed to stream objects (which safes memory), meaning that both¹ lists of input objects usually aren't (shouldn't be) resident in memory. Normally they are retrieved from somewhere else (i.e. a remote server or a disk). Also, the output usually matters where linq solutions are fast but might easily put you on the wrong foot in drawing conclusions because linq literally defers the execution (lazy evaluation), see also: fastest way to get a uniquely index item from the property of an array.
In other words, if it comes to (measuring) performance in PowerShell, it is important to look to the complete end-to-end solution, which is more likely to look something like:
import-csv .\reservations.csv |LeftJoin (import-csv .\leases.csv) -On IP |Export-Csv .\results.csv
(1) Note: unfortunately, there is no easy way to build two parallel input streams (see: #15206 Deferred input pipelines)
(more) Examples
More examples can be found in the related Stack Overflow questions at:
Combining Multiple CSV Files
Combine two CSVs - Add CSV as another Column
CMD or Powershell command to combine (merge) corresponding lines from two files
Can I use SQL commands (such as join) on objects in powershell, without any SQL server/database involved?
Powershell match properties and then selectively combine objects to create a third
Compare Two CSVs, match the columns on 2 or more Columns, export specific columns from both csvs with powershell
Merge two CSV files while adding new and overwriting existing entries
Merging two CSVs and then re-ordering columns on output
Efficiently merge large object datasets having multiple matching keys
Is there a PowerShell equivalent of paste (i.e., horizontal file concatenation)?
How to compare two CSV files and output the rows that are in either of the file but not in both
How to join two CSV files in Powershell with SQL LIKE syntax
How can merge 3 cycle and export in one table
Merging two arrays object into one array object in powershell
And in the Join-Object test script.
Please give a 👍 if you support the proposal to Add a Join-Object cmdlet to the standard PowerShell equipment (#14994)

This can also be done using my module Join-Object
Install-Module 'Join-Object'
Join-Object -Left $leases -Right $reservations -LeftJoinProperty 'IP' -RightJoinProperty 'IP'
Regarding performance, I tested against a sample data of 100k lines:
Hashtable example posted by #js2010 run in 8 seconds.
Join-Object by me run in 14 seconds.
LeftJoin by #iRon run in 1 minute and 50 seconds

Here's a simple example using a hashtable. With big arrays, this turns out to be faster.
$leases =
'IP,Name
192.168.1.1,Apple
192.168.1.2,Pear
192.168.1.3,Banana
192.168.1.99,FishyPC' | convertfrom-csv
$reservations =
'IP,MAC
192.168.1.1,001D606839C2
192.168.1.2,00E018782BE1
192.168.1.3,0022192AF09C
192.168.1.4,0013D4352A0D' | convertfrom-csv
$hashRes=#{}
foreach ($resRecord in $reservations) {
$hashRes[$resRecord.IP] = $resRecord
}
$leases | foreach {
$other = $hashRes[$_.IP]
[pscustomobject]#{IP=$_.IP
MAC=$other.MAC
Name=$_.name}
}
IP MAC Name
-- --- ----
192.168.1.1 001D606839C2 Apple
192.168.1.2 00E018782BE1 Pear
192.168.1.3 0022192AF09C Banana
192.168.1.99 FishyPC

Easiest way I've found to Merge two Powershell Objects is using ConvertTo-Json and ConvertFrom-Json
One liner based on the OPs Senario:
$leases | foreach {(ConvertTo-Json $_) -replace ("}$", (ConvertTo-Json ($reservations | where IP -eq $_.IP | select * -ExcludeProperty IP)) -Replace "^{", ",")}
| ConvertFrom-Json
Results in:
IP Name Mac
-- ---- ---
192.168.1.1 Apple 001D606839C2
192.168.1.2 Pear 00E018782BE1
For another example lets make a couple objects:
$object1 = [PSCustomObject]#{"A" = "1"; "B" = "2"}
$object2 = [PSCustomObject]#{"C" = "3"; "D" = "4"}
Merge them together using Json by replacing the opening and closing brackets:
(ConvertTo-Json $object1) -replace ("}$", $((ConvertTo-Json $object2) -Replace "^{", ",")) | ConvertFrom-Json
Output:
A B C D
- - - -
1 2 3 4
Another example using a group of objects:
$mergedObjects = [PSCustomObject]#{"Object1" = $Object1; "Object2" = $Object2}
Object1 Object2
------- -------
#{A=1; B=2} #{C=3; D=4}
Can just do the same again within a foreach:
$mergedObjects | foreach {(ConvertTo-Json $_.Object1) -replace ("}$", $((ConvertTo-Json $_.Object2) -Replace "^{", ",")) | ConvertFrom-Json}
Output:
A B C D
- - - -
1 2 3 4

You can use script block like this
$leases | select IP, NAME, #{N='MAC';E={$tmp=$_.IP;($reservations| ? IP -eq $tmp).MAC}}

Related

Get serialnumber from asset list

Started in recent weeks in a Junior infrastructure role, and begun playing around with powershell to help save some time here and there.
I am trying to do the following:
1- I'm port a CSV file with a single column names asset
2- Perform a "ForEach" check on each line to find the device's serial number
3- Output results to a CSV with two column "asset" and "serialnumber"
I have dabbled in a few areas, and am currently sitting at something like this:
$file1 = Import-Csv -path "c:\temp\assets.csv" | ForEach-Object {
$asset = $_.asset
}
wmic /node:$asset bios get serialnumber
Export-Csv -Path "c:\temp\assetandserial.csv" -NoTypeInformation
As you may or may not see, I tried to set the column labelled "asset" as the variable, however, not sure if I placed it correctly.
I have tried a few other things, but honestly it's all new to me, so I haven't the foggiest idea where to go from here.
wmic is deprecated, and, for rich type support (OO processing), using PowerShell-native cmdlets is preferable in general.
wmic's immediate PowerShell counterpart is Get-WmiObject, which, however, is equally deprecated, in favor of Get-CimInstance.
Important: The command below uses Get-CimInstance, but note that the CIM cmdlets use a different remoting protocol than the obsolete WMI cmdlets. In short: To use the CIM cmdlets with remote computers, those computers must be set up in the same way that PowerShell remoting requires - see this answer for details.
Get-CimInstance Win32_BIOS -ComputerName (Import-Csv c:\temp\assets.csv).asset |
Select-Object #{ n='Asset'; e='PSComputerName' }, SerialNumber |
Sort-Object Asset |
Export-Csv c:\temp\assetandserial.csv -NoTypeInformation
Note the use of member-access enumeration to extract all .asset values directly from the collection of objects returned from Import-Csv.
All computer (asset) names are passed at once to Get-CimInstance, which queries them in parallel. Since the ordering of the responses from the targeted remote machines isn't guaranteed, Sort-Object is used to sort the results.
A calculated property is used with Select-Object to rename the automatically added .PSComputerName property to Asset.

PowerShell : How can I set a variable from a single column in multi-column output?

I am using Get-PhysicalDisk | Format-Table DeviceID, UniqueID to get a listing of drive number and serial number of all drives on a Windows 2016 Server. I want to search for one serial number and capture only the drive number as a variable. I'm used to awk in UNIX and I'm totally stumped on how to achieve this in PowerShell.
Get-PhysicalDisk | Format-Table DeviceID, UniqueID
DeviceID UniqueID
-------- --------
5 624A937024897B4FF488CBF800027A4B
8 624A937024897B4FF488CBF800028A4D
7 624A937024897B4FF488CBF800027A59
0 {c4d394f5-509e-11e9-a834-806e6f6e6963}
1 {c4d394f6-509e-11e9-a834-806e6f6e6963}
2 {c4d394f7-509e-11e9-a834-806e6f6e6963}
3 {c4d394f8-509e-11e9-a834-806e6f6e6963}
4 {c4d394f9-509e-11e9-a834-806e6f6e6963}
6 624A937024897B4FF488CBF800027A56
I want to expand this command to find SerialNumber 624A937024897B4FF488CBF800027A56 then set a variable called $DriveNumber to the value of 6 as shown in the output.
I then plan to use this variable in Set-Disk to take the drive offline/online as a perform a volume overwrite. I don't want to hard code the drive number because upon reboot, the drive number could change.
NOTE I was using Get-Disk and piping the appropriate output to Set-Disk to perform my drive off/online. But, I have a mysterious issue of the virtual drives not displaying with Get-Disk, therefore I'm trying to find a workaround with Get-PhysicalDisk Thanks!
$driveNumber = (
Get-PhysicalDisk | Where-Object UniqueId -eq '{624A937024897B4FF488CBF800027A56}'
).DeviceId
Note the need to enclose the GUID string in {...}.
As all PowerShell cmdlets do, Get-PhysicalDisk outputs objects whose properties you can query.
Cmdlet Where-Object acts as a filter on the objects it receives from the pipeline and compares the value of property UniqueId to the specified literal GUID (string), which, by definition, matches (at most) one object.
(...).DeviceId returns the value of the target objects' DeviceId property and assigns it to variable $driveNumber.
A note re use of Format-* cmdlets such as Format-Table:
Only ever use Format-* cmdlets for display formatting.
If the intent is further programmatic processing:
either: simply access the input objects' intrinsic properties (whose availability is independent of whether they display by default or via a Format-* cmdlet call)
or: if you need to create simplified or transformed objects with only a subset of the original properties and/or transformed property property values (calculated properties, use Select-Object.

Passing subset of objects down pipeline, based on count of properties?

I need to script up some things in PowerCLI (VMWare's bolt on to PowerShell). Basically we have a server cluster with three hosts. Each host has multiple virtual switches. Each virtual switch has multiple vlans ('port groups' in VMWare speak). I need to audit the fact that the same port groups exist on each host (so things keep working if the VM is moved).
Step 1 to achieving this is would be to know that the port group name exists on each of the three host machines.
I'm falling over with how to filter some objects out of all the ones returned by a cmdlet, based on number of results returned from a property of those objects. I then need to perform further operations with original object type that passes the filter test to go on down the pipeline.
To give some specifics, this an example showing 'Some PortGroup Name' and the three hosts it exists on (and as a bonus, the vSwitch):
Get-VirtualPortGroup -Name 'Some PortGroup Name' |
Select-Object Name, VMHostID, VirtualSwitchId
produces the output
Name VMHostId VirtualSwitchId
---- -------- ---------------
Some PortGroup Name HostSystem-host-29459 key-vim.host.VirtualSwitch-vSwitch6
Some PortGroup Name HostSystem-host-29463 key-vim.host.VirtualSwitch-vSwitch6
Some PortGroup Name HostSystem-host-29471 key-vim.host.VirtualSwitch-vSwitch6
Instead of 3, I'm starting with the 1849 port group names that are being returned by Get-VirtualPortGroup. I need a pipeline to whittle the number of VirtualPortGroup objects down to a collection consisting of only those objects where a count of the 'VMHostId' property is less than 3, and pass the remaining VirtualPortGroup objects down the pipeline for further processing.
This seems simple enough to do. I'm still failing though.
The following almost works. Piping it to measure shows a count of 229, instead of the original 1849 (so it's definitely filtered a lot out, and is possibly correctly returning the subset I'm after...?). The problem is, the object type is now a 'Group' or something at this point in the pipeline, and doesn't have all the properties and methods of the original Get-VirtualPortGroup objects.
Get-VirtualPortGroup |
Group-Object -Property Name |
Where-Object $_.Count -lt 3
Bolting a | Select-Object -ExpandProperty Group to the end of the above seemed promising, except it then seems to return the entire collection of Get-VirtualPortGroup objects as though I had done no filtering in there at all....
Am I doing something fundamentally wrong?
How can I filter out objects based on the count of the number of results returned by a specific property of an object, but still pass the original object type down the pipe?
Your approach is correct, but you got the Where-Object syntax wrong. The abbreviated syntax is:
Where-Object <property> <op> <value>
without the current object variable ($_). In your case that would be:
Where-Object Count -lt 3
Otherwise you must use scriptblock notation:
Where-Object { $_.Count -lt 3 }
This should do what you want:
Get-VirtualPortGroup |
Group-Object -Property Name |
Where-Object { $_.Count -lt 3 } |
Select-Object -Expand Group

How can I prevent from forming multiple tables in a Powershell foreach?

I'm developing a script that connects to Azure AD and extracts failed login for any user so I'm probably going to get more than a row for user.
I have this code in a foreach (there is anything after part of code):
$ConvertedOutput | Select-Object #{Label="UserId"; Expression={$_.UserId}},
#{Label="CreationTime"; Expression={$_.CreationTime}},
#{Label="UserAgent"; Expression={$FinalOutput[0]."Value"}},
#{Label="Operation"; Expression={$_.Operation}},
#{Label="LogonError"; Expression={$_.LogonError}},
#{Label="ClientIP"; Expression={$_.ClientIP}} | Format-Table
How can I prevent from forming multiple tables? I only wanted the table for the first record, then additional records under the same table.
Thanks
here is the output
# Create an empty array to store your results in
[array]$results = #()
# This is your existing loop
foreach (...) {
...
$ConvertedOutput = <your existing code>
...
# Append your object to the results array
$results += $ConvertedOutput | Select-Object ... <your existing code>
}
# Now your results object contains all of the values from inside your loop
# So let's display that!
Write-Output $results
Welcome to stack overflow. I general, it is recommended to supply sample (fake) data in text format rather then pictures (of just headers), the makes life easier for us to answer your question.
Reading your code part, it doesn't add much value unless you planned to further extend it. All expressions generate the same keys and values as the original object, meaning that you can simplify this to just: Select-Object UserId, CreationTime, UserAgent, Operation, LogonError, LogonError, ClientIP or even: Select-Object * (or just omit the complete Select-Object), if you do not select a column subset.
With regards to your question,
By default PowerShell normally concatenates the output by it self, meaning that there is probably something else (that you are not sharing, e.g. a Write-Host command) that causes the data to be released preliminary from the pipeline.
Let me show this with fictive object lists created on the fly from three separate CSV lists:
$Result = &{
ConvertFrom-Csv #'
Id,FirstName,LastName
1,Nancy,Davolio
2,Andrew,Fuller
3,Janet,Leveling
'#
ConvertFrom-Csv #'
Id,FirstName,LastName
4,Margaret,Peacock
5,Steven,Buchanan
6,Michael,Suyama
'#
ConvertFrom-Csv #'
Id,FirstName,LastName
7,Robert,King
8,Laura,Callahan
9,Anne,Dodsworth
'#
}
With the above command, $Result contains the following data:
PS C:\> $Result
Id FirstName LastName
-- --------- --------
1 Nancy Davolio
2 Andrew Fuller
3 Janet Leveling
4 Margaret Peacock
5 Steven Buchanan
6 Michael Suyama
7 Robert King
8 Laura Callahan
9 Anne Dodsworth
One important thing to mention here, is that the columns of the three list should be have the same columns (or at least the first row should contain all expected columns), see: Not all properties displayed
If this doesn't help you further, I recommend you to add you more details to your question.

Comparing two text files and only keeping unique values

All,
I am VERY new to powershell and am attempting to write a script and have run into an issue.
I currently have two text files. For argument sake the first can be called required.txt and the second can be called exist.txt.
I have a script which queries a server and determines a list of all existing groups and writes these to a text file. At the same time the customer has a list of new groups they wish to create. I want to compare the new list (required.txt) with the existing list (exist.txt) and anything which doesn't exist be piped out to a new text file which is then picked up and imported using another process.
I've got the scripting done to gather the list from the server I just need to know how to do the comparison between the existing and required.
Any suggestions welcome.
Richard
you don't have to use as much variables :
$FinalGroups=Compare-Object (get-content .\required.txt) (get-content .\existing.txt) |
where {$_.SideIndicator -eq "<="} |
select -ExpandProperty inputObject |
sort