Find differences between csv contents and array

Hello,

I’ve got a CSV file that I use for different tasks associated with managing Windows Image files. I’m trying to use a ForEach-Object along with an If/Else statement that can find a new item in the list of src_names in the CSV file, and when it finds it, to take action on it. Easier said than done I realize.

However, after running my script, it added all of the images in the CSV list to the WIM file. I then realized that I need a way to compare the two sources first. The Compare-Object looks promising, but I’m having trouble slimming it down so to speak.

I looked at the following posts for some insight:

My first attempt:

Import-Csv "C:\Mounts\WindowsRepairSource\List-SourceNames.csv" | ForEach {
if($($_.src_name) -eq $true){

    Write-Host "Source Name exists"

}else{
    Export-WindowsImage -SourceImagePath "$($_.osd_media)`\$($_.osd_name)`\$($_.osd_sources)`\install.wim" -SourceName "$($_.src_name)" -DestinationImagePath "$($_.wrs_local)`\$($_.wrs_wim)" -CheckIntegrity -CompressionType "max"

}
}

I’m using the object format $($_.src_name) versus a standard variable $src_name because of the ForEach-Object to read the contents of the CSV file.

Basically, I was importing the CSV file, then piping it into a ForEach loop, and then using an If/Else to determine what action to take. I was expecting it to look down the src_name column in the CSV file and see what is new. However, that’s when I realized I wasn’t comparing that list to the contents of the WIM file.

My attempts at comparing:

$wrs_local = "C:\Mounts\WindowsRepairSource"
$csv_names = Import-Csv "$wrs_local\List-SourceNames.csv" | Select @{l='src_name';e={$_.src_name}} # Example from StackOverflow.com
$wim_array = Get-WindowsImage -ImagePath "$wrs_local\RepairSource.wim" | Select-Object ImageName

These two gave me undesired results:

Compare-Object -ReferenceObject $csv_names -DifferenceObject $wim_array -Property src_name -IncludeEqual
Compare-Object -ReferenceObject $csv_names -DifferenceObject $wim_array -Property src_name -PassThru

Trying to create an array for the items that are new (different):

$add_array = Compare-Object $csv_names $wim_array | Where-Object { $_.SideIndicator -eq '<=' } | Foreach-Object { $_.InputObject }

Output:

src_name                       SideIndicator
--------                       -------------
Windows Server 2022 Datacenter <=           

My attempt at sliming it down to just the name of the operating system (src_name) in my CSV file which I’m calling a $csv_array:

$add_array = Compare-Object $csv_names $wim_array | Where-Object { $_.SideIndicator -ne $_.src_name } | Foreach-Object { $_.InputObject } | Select-Object $add_array.src_name

Output:

Windows Server 2022 Datacenter
------------------------------
                              

This is the best I could get for now. Trying to get just the src_name (Windows Server 2022 Datacenter)

I even tried Microsoft’s Example #2 in their Help page for Compare-Object:

# Example 2 - Compare each line of content and exclude the differences
$objects = @{
    $csv_names = Import-Csv "$wrs_local\List-SourceNames.csv" | Select @{l='src_name';e={$_.src_name}}
    $wim_array = Get-WindowsImage -ImagePath "$wrs_local\RepairSource.wim" | Select-Object ImageName
    }
Compare-Object @objects -IncludeEqual -ExcludeDifferent

Unfortunately, that was a dead end; not to mention that excludes the differences and my objective was to capture the differences.

I was hoping to build a new array with just the OS names that I can use to take action on within the If/Else statement above; or possibly a Switch statement.

  1. If src_name in CSV exists in the wim file, skip to the next row.
  2. Else, run this command.

Any insight would be greatly appreciated. Thank you.

I forgot to show a comparison for the two arrays that I’m comparing.
I changed the variable names as well so they were easier to discern.

I’m confused. :woozy_face:

When you create the two arrays to compare them later you should make sure they both have at least one property name in common. You tell Compare-Object to compare them based on the property src_name but the array $wim_array only has one single property and that’s named ImageName. So it does not have the property you told it to do the comparison with.
It’s like having two lists with names - one only with the first names and one only with the last names and trying to figure out who’s related. :wink:

Regardless of that I actually didn’t get what you’re actually trying to do, sorry. :wink:

1 Like

Here is another general tip:

Your code

$wim_array = Get-WindowsImage -ImagePath "$wrs_local\RepairSource.wim" | Select-Object ImageName

With unsing Select-Object you limit yourself with no need to one single property. Even if you may not need it for your next step why bother removing it from your array of objects?

Instead you could use a calculated property to add a property with the needed name for the comaprison like this:

$wim_array = 
    Get-WindowsImage -ImagePath "$wrs_local\RepairSource.wim" | 
        Select-Object -Property *, @{Name = 'src_name'; Expression = { $_.ImageName } }
1 Like

@Olaf,
I’m testing your code now and rethinking my strategy.
Be back shortly…

Hi @Olaf,

Thanks for looking into this. It’s certainly confusing indeed.

I recall seeing something that I can change the name so that they match like you noted. I was unaware of that requirement.
I decided to change the column heading from src_name to ImageName so that it matches the ImageName that I’m pulling from the wim file. (which I changed back after using your code).

However, I just tested your recommended code and realized you did something very similar; only in this case converted the ImageName from the WIM file output to match the src_name column in my CSV file.

Here’s the output using your code (partially cropped):

So, assuming my two arrays can be compared…

My csv_array has Windows Server 2022 Datacenter, while the wim_array does not. I’m able to filter out this difference using this command:

$add_array = Compare-Object $csv_names $wim_array | Where-Object { $_.SideIndicator -ne $_.src_name } | Foreach-Object { $_.InputObject } | Select-Object $add_array.src_name

However, the output is not clean. I need to be able to run my Export-WindowsImage command against that operating system only, and not the other src_name entries in my CSV file/array.

My thought process is to first compare the two sources, find the differences and use those values to take action on.

However, I will be running the loop against the csv_array as it has all of the information needed to Export the Windows Image command against.

Example:

Export-WindowsImage -SourceImagePath "$($_.osd_media)$($.osd_name)\$($_.osd_sources)\install.wim" -SourceName "$($.ImageName)" -DestinationImagePath "$($_.wrs_local)\$($_.wrs_wim)" -CheckIntegrity -CompressionType "max"

I’m starting to think that my approach will not work. However, I wanted to give it a shot.

Perhaps the results of the comparison is placed into an array like what I tried with the $add_array, I can then run the loop on those values in my CSV file, which I think will now call on the csv_array and not the file itself.

I’m not less confused now. :wink:

Could you try to explain in prose what you want to do?

BTW: You don’t need to filter away all properties from your input arrays for comparison. Actually that’s even counterproductive. You should let your input objects unfiltered and even add the parameter -PassThru to your comparison the get the complete objects back with all needed infromation … if I got it right.

(I will go to bed now - my next answer could take a while :sleeping: )

And BTW: The formatting of your code doesn’t help. You may try to fix this.

1 Like

Hi @Olaf,

I use the CSV file to track the operating systems that I need to check for corruption, update, deploy and build/rebuild my Windows Repair Source WIM file (RepairSource.wim).

My note to self: Figure out how to automate adding any images that are not in the RepairSource.wim, but exist in the CSV file.

I foresee needing to add additional Windows Editions to this RepairSource.wim shortly. I normally just do this using this basic code:

#############   Add Windows Image to RepairSource.wim   #############

$SourceImagePath="D:\OSDBuilder\OSMedia\Windows Server 2022 Datacenter x64 Dev 22509.1000\OS\sources\install.wim"
$src_name="Windows Server 2022 Datacenter"
$wrs_wim="C:\Mounts\WindowsRepairSource\RepairSource.wim"
Export-WindowsImage -SourceImagePath "$SourceImagePath" -SourceName "$src_name" -DestinationImagePath "$wrs_wim" -CheckIntegrity -CompressionType "max"

I have a script that builds the RepairSource.wim from scratch with all of the src_names in the CSV file. I could easily just run that script to rebuild it in no time, but I wanted the challenge of being able to automate the process instead, considering I’ve only recently started using this script since you helped me with another/similar one.

Before using this new script, I would have to use the above code to manually add the values needed to add that operating system to the RepairSource.wim.

I figured the best way to automate this process is to compare these two sources:

Using the above table, I added Windows Server 2022 Datacenter to the CSV file (left), but now I need to add that Windows Image to the RepairSource.wim file (right) using the above command.

I need to be able to get the differences between the CSV file and the WIM file.
Take those differences - in this case just the Windows Server 2022 Datacenter and use it in the following code:

Export-WindowsImage -SourceImagePath "$SourceImagePath" -SourceName "$src_name" -DestinationImagePath "$wrs_wim" -CheckIntegrity -CompressionType "max"

The src_name is pulled from the comparison, and then the $SourceImagePath, and $wrs_wim is pulled from the CSV file/csv_array.

The src_name is the first column in my CSV file, so I think once that value is matched, it will then pull the relevant SourceImagePath, and $wrs_wim values for that row/record.

If I got it right your task is way easier than you think it is. But therefor it would be nice if you stopped sharing pictures of code or output and instead started sharing your CSV file content formatted as code. :wink:

Hi @Olaf,

I certainly hope so. That would be great news.

Those three values are in my manual code above, but here is what the csv file looks like for those three relevant pieces of information.

One thing to note is that my CSV file uses src_path instead of $SourceImagePath for the column heading.

src_name,src_path,wrs_wim
Windows 10 Pro for Workstations,D:\OSD\Builder\OSMedia\Windows 10 Pro for Workstations x64 21H2 19044.1526\OS\sources\install.wim,RepairSource.wim
Windows 11 Pro for Workstations,D:\OSD\Builder\OSMedia\Windows 11 Pro for Workstations x64 21H2 22000.493\OS\sources\install.wim,RepairSource.wim
Windows 11 Pro,D:\OSD\Builder\OSMedia\Windows 11 Pro x64 21H2 22000.493\OS\sources\install.wim,RepairSource.wim

Thank you,

That’s ok. I just wanted to show how it could work.

That doesn’t matter. Or it actually gives me the chance to show how to deal with not matching property names. :wink:

Here we import your CSV data:

$CSVInput = @'
src_name,src_path,wrs_wim
Windows 10 Pro for Workstations,D:\OSD\Builder\OSMedia\Windows 10 Pro for Workstations x64 21H2 19044.1526\OS\sources\install.wim,RepairSource.wim
Windows 11 Pro for Workstations,D:\OSD\Builder\OSMedia\Windows 11 Pro for Workstations x64 21H2 22000.493\OS\sources\install.wim,RepairSource.wim
Windows 11 Pro,D:\OSD\Builder\OSMedia\Windows 11 Pro x64 21H2 22000.493\OS\sources\install.wim,RepairSource.wim
'@ | 
    ConvertFrom-Csv

Now we simulate the output of Get-WimImage, filtered to just the name of OS:

$GetWinImageOutput = @'
SourceImagePath
Windows 10 Pro for Workstations
Windows 11 Pro for Workstations
Windows 11 Pro
Windows Server 2022 Datacenter
'@ | 
    ConvertFrom-Csv

Now we deal with the property non matching names:

$CSVData = 
    $CSVInput | 
        Select-Object -Property *, @{Name = 'SourceImagePath'; Expression = {$_.src_Name} }

Now that we prepared our input data we can compare them and save the result in a variable:

$Result = 
    Compare-Object -ReferenceObject $CSVData -DifferenceObject  $GetWinImageOutput -Property SourceImagePath -PassThru -IncludeEqual

And the result looks like this:

PS D:\sample> $Result

src_name        : Windows 10 Pro for Workstations
src_path        : D:\OSD\Builder\OSMedia\Windows 10 Pro for Workstations x64 21H2 19044.1526\OS\sources\install.wim
wrs_wim         : RepairSource.wim
SourceImagePath : Windows 10 Pro for Workstations
SideIndicator   : ==

src_name        : Windows 11 Pro for Workstations
src_path        : D:\OSD\Builder\OSMedia\Windows 11 Pro for Workstations x64 21H2 22000.493\OS\sources\install.wim
wrs_wim         : RepairSource.wim
SourceImagePath : Windows 11 Pro for Workstations
SideIndicator   : ==

src_name        : Windows 11 Pro
src_path        : D:\OSD\Builder\OSMedia\Windows 11 Pro x64 21H2 22000.493\OS\sources\install.wim
wrs_wim         : RepairSource.wim
SourceImagePath : Windows 11 Pro
SideIndicator   : ==

SourceImagePath : Windows Server 2022 Datacenter
SideIndicator   : =>

As you can see - there are all information from the input objects PLUS the comparison information represented by the SideIndicator. Assumed the ouput of Get-WimImage provides actually more than just the name you should be able to do further steps according to the comparison result and the properties from the original objects.

1 Like

Hi @Olaf,

The more I mess with this, the more I realize I’m way over my head.

However, the good news is that I found what I was looking for before posting my defeat. :woozy_face:

I was struggling with how to call on the contents of your $Result array.

Along that journey, I found out how to slim down the $Result array so that it only contained the objects that were different using the SideIndicator for “<=
I piped the following to your code which I found buried in someone’s blog.

$Result = Compare-Object -ReferenceObject $CSVData -DifferenceObject $GetWimImageOutput -Property SourceImagePath -PassThru -IncludeEqual `
| Select-Object * | Where-Object {$_.SideIndicator -like "<="}

This returned the differences only. In this case, src_name Windows Server 2022 Datacenter.

PS C:\Windows\system32> $Result

src_name        : Windows Server 2022 Datacenter
wrs_path        : C:\Mounts\WindowsRepairSource
wrs_wim         : RepairSource.wim
osd_media       : D:\OSD\Builder\OSMedia
osd_builds      : D:\OSD\Builder\OSBuilds
osd_name        : Windows Server 2022 Datacenter x64 Dev 22509.1000 2203050646
osd_sources     : OS\sources
src_mnt         : C:\Mounts\mnt1
wrs_mnt         : C:\Mounts\mnt2
SourceImagePath : Windows Server 2022 Datacenter
SideIndicator   : <=

Now I just need to use these results to add that Windows Server 2022 Datacenter image to the RepairSource.wim file.
Looking at ways to access this data, I eventually realized that I just needed to do the following:

$Result | ForEach-Object {
Export-WindowsImage -SourceImagePath "$($_.osd_media)`\$($_.osd_name)`\$($_.osd_sources)`\install.wim" -SourceName "$($_.src_name)" -DestinationImagePath "$($_.wrs_path)`\RepairSource.wim" -CheckIntegrity -CompressionType "max"
}
Get-WindowsImage -ImagePath "C:\Mounts\WindowsRepairSource\RepairSource.wim"

I run the Get-WindowsImage to verify that only Windows Server 2022 Datacenter was added to the RepairSource.wim file.
As you can see, the OS was added in the number six spot as expected.

ImageIndex       : 6
ImageName        : Windows Server 2022 Datacenter
ImageDescription : (Recommended) This option omits most of the Windows graphical environment. Manage with a command prompt and PowerShell, or remotely with Windows 
                   Admin Center or other tools.
ImageSize        : 8,044,049,640 bytes

I’ve been playing with this for the last hour at least, now I need to rest my brain.

However, I’m extremely grateful for your help with this. I never thought that just gathering and manipulating data was so challenging. I’ve never been good at it, but now it’s time to hone these skills as I still have many more scripts and tasks that I need to improve on as it relates to managing and deploying Windows operating systems.

Thank you VERY much!

I’m glad to hear that you found what you needed.

Unfortunately I’m afraid you still didn’t get how Select-Object works.
Your code:

$Result = Compare-Object -ReferenceObject $CSVData -DifferenceObject $GetWimImageOutput -Property SourceImagePath -PassThru -IncludeEqual `
| Select-Object * | Where-Object {$_.SideIndicator -like "<="}

This way Select-Object does not make any sense at all. Why did you put it there?

That was part of the code snippet that I appended from that blog post I mentioned earlier.

I changed it to this, and it produced the same results.

$Result = Compare-Object -ReferenceObject $CSVData -DifferenceObject $GetWimImageOutput -Property SourceImagePath -PassThru -IncludeEqual `
| Where-Object {$_.SideIndicator -like "<="}