Find items which are in array1 but NOT in array2 - arrays

I extracted two lists of computers from two different tools, Array1 and Array2.
Now I need to extract the ones which are in Array1, but not in Array2.
I managed to get all the matching ones by doing this:
$matchingComp = #()
foreach ($SCCMcomputer in $SCCMcomputers) {
foreach ($eWPTcomputer in $eWPTcomputers) {
if ($SCCMcomputer.Computername -eq $eWPTComputer.Computername) {
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $SCCMcomputer.Computername
$matchingComp +=$obj
}
}
}
$matchingComp | Export-Csv $inEWPT -Delimiter "," -NoTypeInformation
But I still need the ones that are in $SCCMcomputer but NOT in $eWPTcomputers...
I've found some solutions on SO with other languages (e.g. Perl) but not for PowerShell.
UPDATE
I still don't get the correct output, in Excel with this formula:
the output looks like:
means some are here, some not. The output in powershell is like this
means 0KB is emtpy.
$SCCMcomputers | Export-Csv $sccmexport -Delimiter "," -NoTypeInformation
$eWPTcomputers | Export-Csv $ewptexport -Delimiter "," -NoTypeInformation
Compare-Object -ReferenceObject $SCCMcomputers -DifferenceObject $eWPTcomputers | ?{$_.sideIndicator -eq "=>"} |select inputobject | Export-Csv $inEWPT -NoTypeInformation
Compare-Object -ReferenceObject $SCCMcomputers -DifferenceObject $eWPTcomputers | ?{$_.sideIndicator -eq "=="} |select inputobject | Export-Csv $inBoth -NoTypeInformation
Compare-Object -ReferenceObject $SCCMcomputers -DifferenceObject $eWPTcomputers | ?{$_.sideIndicator -eq "<="} |select inputobject | Export-Csv $inSCCM -NoTypeInformation
And both Column Name (or what it's called) from SCCMcomptuers/eWPTcomputers is "Computername"
Any idea what I could be doing wrong? Both computer arrays are generated from SQL and in hashtables (I think it's called): #{Computername=......}#{Computername...., something like this.
Update 2
foreach ($t in $sccmComputers) {
$Test1 += $t.computername
}
$Test2 = #()
foreach ($t in $ewptComputers) {
$Test2 += $t.computername
}
By removing the Header of the Hashtable and just having arrays full of strings works fantasctic..... even -Property computername did not work... :S

use compare-object cmdlet
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt | ?{$_.sideIndicator -eq "<="} |select inputobject
example :
$sccm=#(1,2,3)
$wpt=#(2,4)
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt -IncludeEqual
will output :
InputObject SideIndicator
2 ==
4 =>
1 <=
3 <=
that means value "2" is on both objects, "1" and "3" only on "the left side" (ie the reference object), while "4" is only on the difference object

Use compare-object as follows
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt -passthru
This should give you just the objects in $sccm but not in $wpt.
CORRECTION:
The above code WILL work for the case where DifferenceObject is guaranteed to be a subset of ReferenceObject. It will FAIL, though, if there are additional objects in DifferenceObject that are not also present in ReferenceObject. The above code returns any objects which are present in EITHER ReferenceObject OR DifferenceObject but NOT in both.
To properly return ONLY objects in ReferenceObject that are not also present in DifferenceObject, the following code is required:
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt |
Where-Object { $_.SideIndicator -eq '<=' } |
ForEach-Object { Write-Output $_.InputObject }
The where-object clause ensures only objects that are present in ReferenceObject are passed down the pipeline.
The foreach-object clause forces the output back to a simple array (ref: Converting Custom Object arrays to String arrays in Powershell - thanks Keith)

You can use -contains and -notcontains
$A1 ="asd","zxc","qwe",'a'
$A2 = "asd","zxc","bqwe",'b'
$A1,$A2 |
%{
$_|
%{
If ($A1 -contains $_ -and $A2 -notcontains $_) {$_}
}
}

The Compare-Object method is indeed the best. What hasn't been addressed yet is clean output to your Excel file.
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '<=' | Export-Csv sccm-only.csv -NoTypeInformation
will produce two columns. One with "InputObject" and your computer names, and another column with "SideIndicator" and a bunch of rows with "<=".
The easy fix is to select only the column you want:
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '<=' | Select-Object InputObject | Export-Csv sccm-only.csv -NoTypeInformation
This will give you a single column labeled "InputObject" and your computer names.
If you want to change the column label, use the method from another thread, Windows Powershell Rename Column Heading CSV file:
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '<=' | Select-Object #{ expression={$_.InputObject}; label='ComputerName' } | Export-Csv sccm-only.csv -NoTypeInformation
Also, simply change the SideIndicator comparison to get those computers in both systems, or only in eWPT:
# Only in eWPT
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '=>' | Select-Object #{ expression={$_.InputObject}; label='ComputerName' } | Export-Csv sccm-only.csv -NoTypeInformation
# In both SCCM and eWPT
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '==' | Select-Object #{ expression={$_.InputObject}; label='ComputerName' } | Export-Csv sccm-only.csv -NoTypeInformation

$sccm=#(1,2,2,3)
$wpt=#(2,4)
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt |
Where-Object { $_.SideIndicator -eq '<=' } |
ForEach-Object { Write-Output $_.InputObject }
This will return 1,2,3, this method is not correct

Related

Can I combine these two arrays

I have some simple Exchange Powershell I have written. I would like to list the UPN, Displayname, Item Count, and Item Size into a single CSV. However I have only been able to successfully push the data to two arrays and then manually combine them. Here is my code.
$MailBoxs = Get-Mailbox * | Select UserPrincipalName -ExpandProperty UserPrincipalName | Sort-Object UserPrincipalName
$Mailboxs2 = $MailBoxs.Where({ $_ -ne $null })
ForEach($MailBox2 in $MailBoxs2) { Get-MailboxStatistics $Mailbox2 | Sort-Object TotalItemSize –Descending | Select #{label=”User”;expression={$_.DisplayName}},#{label=”Total Size (MB)”;expression={$_.TotalItemSize.Value.ToMB()}},#{label=”Items”;expression={$_.ItemCount}} | Export-CSV "C:\T2\MailBoxSize.csv" -Append -NoTypeInformation }
ForEach($MailBox2 in $MailBoxs2) { $Mailbox2 | Export-CSV "C:\T2\MailBoxSize2.csv" -Append -NoTypeInformation }
Basically the second CSV gives me two fields for some reason the SMTP address and some random Length field, It also gives me a leading whitespace. If anyone has any ideas on how to clean this up I would love to hear them. Thanks for your time.
The multiple select statements were unnecessary. Here's a bit simplified way:
$mailboxes = #(Get-Mailbox *).
Where({$_.UserPrincipalName}) |
Sort-Object -Property UserPrincipalName
foreach ($box in $mailboxes) {
Get-MailboxStatistics $box.UserPrincipalName |
Sort-Object -Property TotalItemSize -Descending |
Select-Object -Property #(
#{L='UPN';E={$box.UserPrincipalName}}
#{L='User';E={$_.DisplayName}}
#{L='Total Size (MB)';E={$_.TotalItemSize.Value.ToMB()}}
#{L='Items';E={$_.ItemCount}}
) |
Export-Csv -Path 'C:\T2\MailBoxSize.csv' -NoTypeInformation -Append
}

Parsing an array of ojects into strings in PowerShell

I am calling Get-AdComputer like this:
[string[]] $computer = Get-ADComputer -properties * -filter {(operatingsystem -like "*Windows 7*")} |
Where-Object {$_.name -like "*-*"} |
Where-Object {$_.name -NotLike "V7-*"} |
Where-Object {$_.name -NotLike "*-NONE"} |
Where-Object {$_.name -NotLike "*-ONCALL"} |
Where-Object {$_.name -NotLike "*-BLACKBAUD"} |
Where-Object {$_.name -NotLike "SC-WIN7-1"} |
Where-Object {$_.name -NotLike "UT-SWCLIENT-01"} |
Select-Object Name, LastLogonDate
This creates a string array and I can access it like this:
$computer[1]
which returns
#{Name=W7-9HQPR3J; LastLogonDate=05/08/2017 09:45:13}
I need to output the data to a csv file in two columns Name, LastLogonDate
Unfortunately when I call $Computer | out-file -filepath $ServiceTagsPath -Force -Width 200 -Encoding ascii
I get one column with the headings on each line:
My other requirement is to be able to use the Name column in web-service calls to get warranty information...
so how could I do that?
I would not cast the output to array of strings [string[]] but leave the casting to powershell.
Then you can use command Export-Csv which works better with array of objects.
$computer = Get-ADComputer -properties * -filter {(operatingsystem -like "*Windows 7*")} |
Where-Object {$_.name -like "*-*"} |
Where-Object {$_.name -NotLike "V7-*"} |
Where-Object {$_.name -NotLike "*-NONE"} |
Where-Object {$_.name -NotLike "*-ONCALL"} |
Where-Object {$_.name -NotLike "*-BLACKBAUD"} |
Where-Object {$_.name -NotLike "SC-WIN7-1"} |
Where-Object {$_.name -NotLike "UT-SWCLIENT-01"} |
Select-Object Name, LastLogonDate
$computer | Export-Csv "test.csv" -NoTypeInformation

wildcard in array

How can I use the 1709 as a wildcard? The value 1709 is stored in an array as $MoveItem.Version, but I can't figure out how do a -like, when the value comes from an array, as I can't put in a wildcard *. I also tried to do a match.
The file name looks like this: REFW10-X86-1709_01-12-2017.wim.
The below code works fine, but I would like to automate it, so everything comes from the array. Is that possible?
Get-ChildItem -Path $OSPathTemp -Recurse | Where {
($_.Name -eq $MoveItem.File) -and
($_.Name -like "*1709*")
} | Select-Object -ExpandProperty FullName
$MoveItem.Version contains 1607,1706,1709. I would like to choose only the one with 1709. The final output should look like this:
foreach ($MoveItem in $MoveItems) {
Get-ChildItem -Path $OSPathTemp -Recurse | Where {
($_.Name -eq $MoveItem.File) -and
($_.Name -like $MoveItem.Version)
} | Select-Object -ExpandProperty FullName
}
The Array looks like this:
$MoveItem = #(
[pscustomobject]#{File="REFW10-X86-1709_01-12-2017.wim";Version=1709}
[pscustomobject]#{File="REFW10-X86-1706_01-12-2017.wim";Version=1706}
)
So you have a hash table (or similar) named $MoveItem that has a .File property that is a filename, and you have a .Versions property that's a string array?
Test name: REFW10-X86-1709_01-12-2017.wim
Get-ChildItem -Path $OSPathTemp -Recurse |
ForEach-Object {
If ($_.Name -match '-\d{4}_') { $Version = $Matches[0] }
If ($Version -in $MoveItem.Versions -and
$_.Name -eq $MoveItem.File) { $_.FullName }
}

PowerShell finding duplicates in CSV and outputting different header

I guess the question is in the title.
I have a CSV that looks something like
user,path,original_path
I'm trying to find duplicates on the original path, then output both the user and original_path line.
This is what I have so far.
$2 = Import-Csv 'Total 20_01_16.csv' | Group-Object -Property Original_path |
Where-Object { $_.count -ge 2 } | fl Group | out-string -width 500
This gives me the duplicates in Original_Path. I can see all the required information but I'll be danged if I know how to get to it or format it into something useful.
I did a bit of Googleing and found this script:
$ROWS = Import-CSV -Path 'Total 20_01_16.csv'
$NAMES = #{}
$OUTPUT = foreach ( $ROW in $ROWS ) {
IF ( $NAMES.ContainsKey( $ROW.Original_path ) -and $NAMES[$ROW.original_path] -lt 2 )
{ $ROW }
$NAMES[$ROW.original_path] += 1 }
Write-Output $OUTPUT
I'm reluctant to use this because, well first I have no idea what it's doing. So little of the makes any sense to me, I don't like using scripts I can't get my head around.
Also, and this is the more important part, it's only giving me a single duplicate, it's not giving me both sets. I'm after both offending lines, so I can find both users with the same file.
If anyone could be so kind as to lend a hand I'd appreciate it.
Thanks
It depends on the output format you need, but to build on what you already have we can use this to show the records in the console:
Import-Csv 'Total 20_01_16.csv' |
Group-Object -Property Original_path |
Where-Object { $_.count -ge 2 } |
Foreach-Object { $_.Group } |
Format-Table User, Path, Original_path -AutoSize
Alternatively, use this to save them in a new csv-file:
Import-Csv 'Total 20_01_16.csv' |
Group-Object -Property Original_path |
Where-Object { $_.count -ge 2 } |
Foreach-Object { $_.Group } |
Select User, Path, Original_path |
Export-csv -Path output.csv -NoTypeInformation

Compare Patch Levels across Multiple servers

Hi all I was working on a script to compare a list of patch levels between multiple servers and show the list of patches missing on either of server. The script should compare between each server within the array and give the output i was trying using Get-Hotfix and also using compare-object to compare and get the server name evaluating $_.sideindicator -match "=>" and $_.sideindicator -match "<=".
can anyone please help further?
here's the code till now for four servers, if there are n number of servers i wanted the logic on how to proceed.
$array=#()
$serd1 = Get-HotFix -ComputerName serd1 | select -ExpandProperty hotfixid
$serd2 = Get-HotFix -ComputerName serd2 | select -ExpandProperty hotfixid
$serd3 = Get-HotFix -ComputerName serd3 | select -ExpandProperty hotfixid
$serd4 = Get-HotFix -ComputerName serd4 | select -ExpandProperty hotfixid
$check1 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd2 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check1 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check1 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$check2 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd3 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check2 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check2 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$check3 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check3 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check3 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$check4 = Compare-Object -ReferenceObject $serd2 -DifferenceObject $serd3 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check4 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check4 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$check5 = Compare-Object -ReferenceObject $serd2 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check5 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check5 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$check6 = Compare-Object -ReferenceObject $serd3 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check6 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check6 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$array
The question is how to make this script work for random number of servers
This question is old but thought this might help out if anyone else finds this and thinks it is something they'd want to give a go.
Anyway, to meet the requirements stated here, see if this helps - let me know if anyone finds problems:
$Servers = "SERVER1", "SERVER2", "SERVER3", "SERVER4"
$MissingPatches=#()
$ServerPatches = New-Object 'object[]' $($Servers.Length)
for($i=0; $i -lt $($Servers.Length); $i++)
{
$ServerPatches[$i] = Get-HotFix -ComputerName $Servers[$i] | select -ExpandProperty hotfixid
}
for($i=0; $i -lt $($Servers.Length); $i++)
{
for($j=($i+1); $j -lt $($Servers.Length); $j++)
{
$Compare = Compare-Object -ReferenceObject $ServerPatches[$i] -DifferenceObject $ServerPatches[$j] -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$MissingPatches += $Compare | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={$Servers[$i]}},#{l="MissingPatches";e={$_.inputobject}}
$MissingPatches += $Compare | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={$Servers[$j]}},#{l="MissingPatches";e={$_.inputobject}}
}
}
$MissingPatches = foreach($MissingPatch in $MissingPatches) {
if($($MissingPatch.MissingPatches) -ne "File 1")
{
$MissingPatch
}
}
$MissingPatches | ft -AutoSize
Going down this path I started thinking of potential improvements. So, if I have time I might rewrite and create a patch object that indicates on what server it is present and where it is missing for more detail of why a patch is indicated as "Missing".
When a patch is found on any server it is added to that array. A property of PresentOn will be updated with the server name it was found and the servers so far that didn't contain the patch can be added to a property of MissingOn, then checked continuously going forward. Then, a table of all patches can be displayed by these statuses vs. printing out only a "Missing" array. Could take some doing but might be worth the work. I'm also seeing this being a potentially costly script based on the number of patches in play and servers. But, if anyone else gets to it first (or finds flaws in the approach) let me know here!
This should work:
$servers = "one", "two", "three"
$array = #()
for ($i = 0; $i -lt ($servers.Count - 1); $i++)
{
$serd1 = Get-HotFix -ComputerName $servers[$i] | select -ExpandProperty hotfixid
$serd2 = Get-HotFix -ComputerName $servers[$i+1] | select -ExpandProperty hotfixid
$array += $check1 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check1 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
}
$array

Resources