I've been wanting an easy to use script that will allow me to replace multiple strings from multiple files for a while. So far I've got this code:
$replacements = #{
'bCompressDiffuseLocalPlayerCharacterTextures=True' = 'bCompressDiffuseLocalPlayerCharacterTextures=False'
'bCompressDiffuseLocalPlayerVehicleTextures=True' = 'bCompressDiffuseLocalPlayerVehicleTextures=False'
'bCompressDiffuseOtherPlayerCharacterTextures=True' = 'bCompressDiffuseOtherPlayerCharacterTextures=False'
'bCompressDiffuseOtherPlayerVehicleTextures=True' = 'bCompressDiffuseOtherPlayerVehicleTextures=False'
'bCompressNormalTextures=True' = 'bCompressNormalTextures=False'
'bDisablePhysXHardwareSupport=True' = 'bDisablePhysXHardwareSupport=False'
'bEnableMouseSmoothing=True' = 'bEnableMouseSmoothing=False'
'bInitializeShadersOnDemand=True' = 'bInitializeShadersOnDemand=False'
'MaxChannels=32' = 'MaxChannels=64'
'MotionBlur=True' = 'MotionBlur=False'
'm_bCalculateOnServer=True' = 'm_bCalculateOnServer=False'
'OneFrameThreadLag=True' = 'OneFrameThreadLag=False'
'PoolSize=140' = 'PoolSize=1024'
'UseMinimalNVIDIADriverShaderOptimization=True' = 'UseMinimalNVIDIADriverShaderOptimization=False'
'UseTextureFileCache=False' = 'UseTextureFileCache=True'
}
function Update-FileContent {
[cmdletbinding()]
param(
[Parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Mandatory=$true,
Position=0)]
[Alias('PsPath')]
$Path
)
$lines = Get-Content $Path
$lines | ForEach-Object {
foreach($rep in $replacements.Keys)
{
$_ = $_ -replace $rep, $replacements[$rep]
}
$_
} | Set-Content $Path
}
Get-ChildItem -Recurse *.ini | Update-FileContent
It works but only if a file is 1 directory deep.
I'd do something like this:
$replacements = #{
'bCompressDiffuseLocalPlayerCharacterTextures=True' = 'bCompressDiffuseLocalPlayerCharacterTextures=False'
'bCompressDiffuseLocalPlayerVehicleTextures=True' = 'bCompressDiffuseLocalPlayerVehicleTextures=False'
'bCompressDiffuseOtherPlayerCharacterTextures=True' = 'bCompressDiffuseOtherPlayerCharacterTextures=False'
'bCompressDiffuseOtherPlayerVehicleTextures=True' = 'bCompressDiffuseOtherPlayerVehicleTextures=False'
'bCompressNormalTextures=True' = 'bCompressNormalTextures=False'
'bDisablePhysXHardwareSupport=True' = 'bDisablePhysXHardwareSupport=False'
'bEnableMouseSmoothing=True' = 'bEnableMouseSmoothing=False'
'bInitializeShadersOnDemand=True' = 'bInitializeShadersOnDemand=False'
'MaxChannels=32' = 'MaxChannels=64'
'MotionBlur=True' = 'MotionBlur=False'
'm_bCalculateOnServer=True' = 'm_bCalculateOnServer=False'
'OneFrameThreadLag=True' = 'OneFrameThreadLag=False'
'PoolSize=140' = 'PoolSize=1024'
'UseMinimalNVIDIADriverShaderOptimization=True' = 'UseMinimalNVIDIADriverShaderOptimization=False'
'UseTextureFileCache=False' = 'UseTextureFileCache=True'
}
function Update-FileContent {
[cmdletbinding()]
param(
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$true, Position=0)]
[Alias('PsPath')]
$Path
)
# read the ini file in as one single string
$content = Get-Content $Path -Raw
# copy that string so we can compare at the end of the loop if anything has changed at all
$newContent = $content
foreach($rep in $replacements.Keys) {
$newContent = $newContent -replace $rep, $replacements[$rep]
}
# only replace the contents of the ini file if changes are made
if ($newContent -ne $content) {
Write-Host "Replacing content of file '$Path'"
Set-Content $Path -Value $content
}
}
$rootPath = '<PATH OF THE ROOTFOLDER TO RECURSE WHERE THE INI FILES ARE LOCATED>'
Get-ChildItem -Path $rootPath -Filter '*.ini' -Recurse | ForEach-Object { Update-FileContent $_.FullName }
Related
My script read a path from TFS and I add a string to it but before I need to verify that the path contains or not contains a sign
Example1:
This is the path, in this case I need to add '/database/'
$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22
Example2: I need to add only 'database/'
$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/
Example3: I need to add '/'
$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/database
The goal is to continue with the script with the path/database/
so I need to check first the path and then to add or remove the 'database' string
Anyone can help me with that please?
If I understand the question properly, you want to check if the path from TFS ends with a forward slash or not, so you would know what to append to it and for thast you could use a small helper function like this:
function Join-TFSPath {
[CmdletBinding()]
param (
[parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()]
[string] $Path,
[parameter(Mandatory = $false, Position = 1)]
[string[]] $ChildPath,
[char]$Separator = '/'
)
if ($ChildPath.Count) {
"{0}$separator{1}$Separator" -f $Path.TrimEnd("\/"),
(($ChildPath | ForEach-Object { $_.Trim("\/") } |
Where-Object { $_ -match '\S' }) -join $Separator)
}
else {
"{0}$separator" -f $Path.TrimEnd("\/")
}
}
# test if you need to add `database` or not
$tfsPath = '$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/database'
$folder = 'database'
if (($tfsPath.TrimEnd("\/") -split '[\\/]')[-1] -ne $folder) {
# use the function adding the $folder as ChildPath
Join-TFSPath -Path $tfsPath -ChildPath $folder
}
else {
# use the function without specifying the ChildPath so it will only ensure it
# ends with the chosen (or in this case default) separator character
Join-TFSPath -Path $tfsPath
}
As per your comment, you could perhaps then use a more dedicated helper function like:
function Append-TFSPath {
[CmdletBinding()]
param (
[parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()]
[string] $Path,
[parameter(Mandatory = $false, Position = 1)]
[string] $ChildPath = 'database',
[char]$Separator = '/'
)
$Path = $Path -replace '[\\/]+$' # trim off final slash(es)
$ChildPath = $ChildPath -replace '^[\\/]|[\\/]$' -replace '\\', $Separator
if ([string]::IsNullOrWhiteSpace($ChildPath) -or ($Path -replace '\\', $Separator) -like "*$ChildPath") {
"{0}$separator" -f $Path
}
else {
"{0}$separator{1}$Separator" -f $Path, $ChildPath
}
}
Then, just send the path as you have received it to the function and it will return the path you want
$tfsPath = '$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/database'
$folder = 'V8.6.22/database'
Append-TFSPath -Path $tfsPath -ChildPath $folder
# because 'database' is the default value for the ChildPath parameter, you can leave that out:
# Append-TFSPath -Path $tfsPath
Testcases:
Append-TFSPath -Path '$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22'
Append-TFSPath -Path '$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/'
Append-TFSPath -Path '$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/database'
Append-TFSPath -Path '$/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/database/'
will all return $/Idu Client-Server/CoreBranches/V6.4/Patches/V8.6.22/database/
I have the following code:
$xmlFile = 'C:\Users\kraer\Desktop\bom.xml'
[xml]$xml = Get-Content $xmlFile
$xml.bom.components.component | ForEach-Object {
$finalObject = [PSCustomObject]#{
'Name' = $_.name
'Version' = $_.version
'License' = $_.licenses.license.id
}
Write-Output $finalObject
}
Now I would like to convert my $finalObject to a MarkDown Table. Are there any possibilities here?
For another question I received this answer but right now it doesn't work for my code.
function ConvertTo-MarkDownTable {
[CmdletBinding()] param(
[Parameter(Position = 0, ValueFromPipeLine = $True)] $InputObject
)
Begin { $Init = $True }
Process {
if ( $Init ) {
$Init = $False
$_.PSObject.Properties.Name -Join '|'
$_.PSObject.Properties.ForEach({ '-' }) -Join '|'
}
$_.PSObject.Properties.Value -Join '|'
}
}
Do you have another solution?
Thanks for your help
Not knowing the contents of your bom.xml, you might try this slightly adapted version of the function:
function ConvertTo-MarkDownTable {
[CmdletBinding()] param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
$InputObject
)
Begin {
$headersDone = $false
$pattern = '(?<!\\)\|' # escape every '|' unless already escaped
}
Process {
if (!$headersDone) {
$headersDone = $true
# output the header line and below that a dashed line
# -replace '(?<!\\)\|', '\|' escapes every '|' unless already escaped
'|{0}|' -f (($_.PSObject.Properties.Name -replace $pattern, '\|') -join '|')
'|{0}|' -f (($_.PSObject.Properties.Name -replace '.', '-') -join '|')
}
'|{0}|' -f (($_.PsObject.Properties.Value -replace $pattern, '\|') -join '|')
}
}
Usage:
# load the xml from file
$xml= New-Object System.XML.XMLDocument
$xml.Load('C:\Users\kraer\Desktop\bom.xml')
$finalObject = $xml.bom.components.component | ForEach-Object {
[PSCustomObject]#{
'Name' = $_.name
'Version' = $_.version
'License' = $_.licenses.license.id
}
}
# convert to markdown
$finalObject | ConvertTo-MarkDownTable
P.S. $_.licenses.license.id might be wrong, because it looks like licenses is an array of licences. You would probably want to do something like this here:
($_.licenses | ForEach-Object { $_.license.id }) -join '; '
I want to create a file and write content to the file.
The new file will create based on the total SWPO file exist. I tried this scirpt, I can create new file. But when I put only 1 $PO_Path file, it will create 2 new file. Actually those 2 file are the same but one of the file without $c. Like this
ID_ABC18XXR3CT123_.job
ID_ABC18XXR3CT123_AE.job
BUt if I put $PO_Path file more than 1, it works well.
Anyone can help me please. Thanks.
Function Create_OriJob
{
$BID = "18XXR3CT123"
$Job_Path = $Config_File.Automation_Config.Path.OriJob
$PO_Path = $Config_File.Automation_Config.Path.POfiles
if(Test-Path -Path "$PO_Path\*$BID*")
{
Write-Output "SWPO File Found"
# Start-Sleep -s 3
$PO_Content = Get-Content -path "$PO_Path\*$BID*"
$POfile = Get-ChildItem -Name "$PO_Path\*$BID*"
$Get_CRM = $PO_Content | Where-Object {$_ -like "*;CRM*"}
$CRM = $Get_CRM.Substring(5,2)
$CRM = $CRM.split()
$POCountry = Get-ChildItem -Name "$PO_Path"
$GetCountry = $POCountry.Substring(15,3)
$GetCountry = $GetCountry.split()
For($i = 0; $i -lt $POfile.Length; $i++){
try{
$po = $POfile[$i]
$c = $CRM[$i]
$cc = $GetCountry[$i]
New-Item -ItemType File -Path "$Job_Path\$JobType`_$Prefix$BID`_$c.job" -Force
$Title = $Config_File.Automation_Config.Out_Job.Title
$Auto = $Config_File.Automation_Config.Out_Job.Auto
$Proc = $Config_File.Automation_Config.Out_Job.Process
$Auto = $Config_File.Automation_Config.Out_Job.Auto
$PO_Conf = $Config_File.Automation_Config.Out_Job.PO
$BIDINFO = $Config_File.Automation_Config.Out_Job.BIDINFO
$BuildID = $Config_File.Automation_Config.Out_Job.BID
$PFX = $Config_File.Automation_Config.Out_Job.PFX
$CRM_Conf = $Config_File.Automation_Config.Out_Job.CRM
$CountryConf = $Config_File.Automation_Config.Out_Job.Country
$Platform = $Config_File.Automation_Config.Out_Job.Platform
$TSJobcreate = Get-Date
$Output_JOB = #"
<?xml version="1.0" encoding="UTF-8"?>
<$Title>
<$Auto>
<$Proc>$Auto</$Proc>
<$PO_Conf>$po</$PO_Conf>
</$Auto>
<$BIDINFO>
<$BuildID>$BID</$BuildID>
<$PFX>$Prefix</$PFX>
<$CRM_Conf>$c</$CRM_Conf>
<$CountryConf>$cc</$CountryConf>
</$BIDINFO>
<$Platform>
$All_SSID
</$Platform>
<Timestamp>
<JobCreate>$TSJobcreate</JobCreate>
</Timestamp>
</$Title>
"#
$Output_JOB | Out-File "$Job_Path\$JobType`_$Prefix$BID`_$c.job" -NoNewline
Write-Host "Output"
}
catch{
Write-Output "Something wrong!"
}
}
Write-Output "Continue to create operational job"
Create_OpJob
}
else{
Write-Host "SWPO Not Found, Do Error checking file"
Error_Monitoring
}
#Error_Monitoring
}
I have a script that I've been working on to provide parsing of SCCM log files. This script takes a computername and a location on disk to build a dynamic parameter list and then present it to the user to choose the log file they want to parse. Trouble is I cannot seem to get the ValidateSet portion of the dynamic parameter to provide values to the user. In addition the script won't display the -log dynamic parameter when attempting to call the function.
When you run it for the first time you are not presented with the dynamic parameter Log as I mentioned above. If you then use -log and then hit tab you’ll get the command completer for the files in the directory you are in. Not what you’d expect; you'd expect that it would present you the Logfile names that were gathered during the dynamic parameter execution.
PSVersion 5.1.14409.1012
So the question is how do I get PowerShell to present the proper Validate set items to the user?
If you issue one of the items in the error log you get the proper behavior:
Here are the two functions that i use to make this possible:
function Get-CCMLog
{
[CmdletBinding()]
param([Parameter(Mandatory=$true,Position=0)]$ComputerName = '$env:computername', [Parameter(Mandatory=$true,Position=1)]$path = 'c:\windows\ccm\logs')
DynamicParam
{
$ParameterName = 'Log'
if($path.ToCharArray() -contains ':')
{
$FilePath = "\\$ComputerName\$($path -replace ':','$')"
if(test-path $FilePath)
{
$logs = gci "$FilePath\*.log"
$LogNames = $logs.basename
$logAttribute = New-Object System.Management.Automation.ParameterAttribute
$logAttribute.Position = 2
$logAttribute.Mandatory = $true
$logAttribute.HelpMessage = 'Pick A log to parse'
$logCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$logCollection.add($logAttribute)
$logValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogNames)
$logCollection.add($logValidateSet)
$logParam = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName,[string],$logCollection)
$logDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$logDictionary.Add($ParameterName,$logParam)
return $logDictionary
}
}
}
begin {
# Bind the parameter to a friendly variable
$Log = $PsBoundParameters[$ParameterName]
}
process {
# Your code goes here
#dir -Path $Path
$sb2 = "$((Get-ChildItem function:get-cmlog).scriptblock)`r`n"
$sb1 = [scriptblock]::Create($sb2)
$results = Invoke-Command -ComputerName $ComputerName -ScriptBlock $sb1 -ArgumentList "$path\$log.log"
[PSCustomObject]#{"$($log)Log"=$results}
}
}
function Get-CMLog
{
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
$Path,
$tail =10
)
PROCESS
{
if(($Path -isnot [array]) -and (test-path $Path -PathType Container) )
{
$Path = Get-ChildItem "$path\*.log"
}
foreach ($File in $Path)
{
if(!( test-path $file))
{
$Path +=(Get-ChildItem "$file*.log").fullname
}
$FileName = Split-Path -Path $File -Leaf
if($tail)
{
$lines = Get-Content -Path $File -tail $tail
}
else {
$lines = get-cotnet -path $file
}
ForEach($l in $lines ){
$l -match '\<\!\[LOG\[(?<Message>.*)?\]LOG\]\!\>\<time=\"(?<Time>.+)(?<TZAdjust>[+|-])(?<TZOffset>\d{2,3})\"\s+date=\"(?<Date>.+)?\"\s+component=\"(?<Component>.+)?\"\s+context="(?<Context>.*)?\"\s+type=\"(?<Type>\d)?\"\s+thread=\"(?<TID>\d+)?\"\s+file=\"(?<Reference>.+)?\"\>' | Out-Null
if($matches)
{
$UTCTime = [datetime]::ParseExact($("$($matches.date) $($matches.time)$($matches.TZAdjust)$($matches.TZOffset/60)"),"MM-dd-yyyy HH:mm:ss.fffz", $null, "AdjustToUniversal")
$LocalTime = [datetime]::ParseExact($("$($matches.date) $($matches.time)"),"MM-dd-yyyy HH:mm:ss.fff", $null)
}
[pscustomobject]#{
UTCTime = $UTCTime
LocalTime = $LocalTime
FileName = $FileName
Component = $matches.component
Context = $matches.context
Type = $matches.type
TID = $matches.TI
Reference = $matches.reference
Message = $matches.message
}
}
}
}
}
The problem is that you have all the dynamic logic inside scriptblock in the if statement, and it handles the parameter addition only if the path provided contains a semicolon (':').
You could change it to something like:
if($path.ToCharArray() -contains ':') {
$FilePath = "\\$ComputerName\$($path -replace ':','$')"
} else {
$FilePath = $path
}
and continue your code from there
PS 6 can do a dynamic [ValidateSet] with a class:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-6#dynamic-validateset-values
I've been trying to compare two hashes of hashes against each other. Unfortunately even with the great help from here I struggled to pull together what I was trying to do.
So, I've resorted to searching the internet again and I was lucky enough to find Edxi's code (full credit to him and Dbroeglin who looks to have written it originally)https://gist.github.com/edxi/87cb8a550b43ec90e4a89d2e69324806 His compare-hash function does exactly what I needed in terms of its comparisons. However, I'd like to report of the full path of the hash in the final output. So I've tried to update the code to allow for this. My thinking being that I should be able to take the Key (aka path) while the code loops over itself but I'm clearly going about it in the wrong manner.
Am I going about this with the right approach, and if not, would someone suggest another method please? The biggest problem I've found so far is that if I change the hash, my code changes don't work e.g. adding 'More' = 'stuff' So it becomes $sailings.Arrivals.PDH083.More breaks the path in the way I've set it.
My version of the code:
$Sailings = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '10:11'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'OnTime'
'Other' = #{'Data' = 'Test'}
}
}
}
$Flights = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port'
'Scheduled' = '09:05'
'Expected' = '10:20'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port11'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'NotOnTime'
'Other' = #{'Data' = 'Test_Diff'}
}
}
}
function Compare-Hashtable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,
[Parameter(Mandatory = $true)]
[Hashtable]$Right,
[string] $path,
[boolean] $trackpath = $True
)
write-host "PAth received as: $path"
function New-Result($Key, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property #{
key = $Key
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
[Object[]]$Results = $Left.Keys | ForEach-Object {
if ($trackpath ) {
write-host "Working on Path: " $path + $_
}
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
write-host "Right key not matched. Report path as: $path"
New-Result $path $Left[$_] "<=" $Null
}
else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
if ($path -ne $null -or $path -ne "/") {
$path = $path + "/" + $_
write-host "Sending Path to function as: $path"
}
Compare-Hashtable $Left[$_] $Right[$_] $path
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
$path = $path + "/" + $_
write-host "Not a hash so must be a value at path:$path"
New-Result $path $LValue "!=" $RValue
}
else {
Write-Host "Before changing path: $path "
if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
$path = $path.Substring(0, $path.lastIndexOf('/'))
Write-Host "After changing path: $path "
}
}
}
}
# if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
# Tried to use this to stop error on substring being less than zero
# but clearly doesnt work when you add more levels to the hash
$path = $path.Substring(0, $path.lastIndexOf('/'))
# } else { $path = $path.Substring(0, $path.lastIndexOf('/')) }
}
$Results += $Right.Keys | ForEach-Object {
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $_ $Null "=>" $Right[$_]
}
}
if ($Results -ne $null) { $Results }
}
cls
Compare-Hashtable $Sailings $Flights
outputs
key lvalue side rvalue
--- ------ ---- ------
/Arrivals/PDH083/Expected 10:11 != 10:20
/Arrivals/GoingTo Port1 != Port
/Departures/PDH083/ArrivingFrom Port1 != Port11
/Departures/PDH083/Status OnTime != NotOnTime
/Departures/PDH083/Other/Data Test != Test_Diff
I won't do that much string manipulation on the $Path but threat $Path as an array and join it to a string at the moment you assign it as a property (key = $Path -Join "/") to the object:
function Compare-Hashtable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$Left,
[Parameter(Mandatory = $true)]
[Hashtable]$Right,
[string[]] $path = #(),
[boolean] $trackpath = $True
)
write-host "Path received as: $path"
function New-Result($Path, $LValue, $Side, $RValue) {
New-Object -Type PSObject -Property #{
key = $Path -Join "/"
lvalue = $LValue
rvalue = $RValue
side = $Side
}
}
$Left.Keys | ForEach-Object {
$NewPath = $Path + $_
if ($trackpath ) {
write-host "Working on Path: " $NewPath
}
if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
write-host "Right key not matched. Report path as: $NewPath"
New-Result $NewPath $Left[$_] "<=" $Null
}
else {
if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
Compare-Hashtable $Left[$_] $Right[$_] $NewPath
}
else {
$LValue, $RValue = $Left[$_], $Right[$_]
if ($LValue -ne $RValue) {
New-Result $NewPath $LValue "!=" $RValue
}
}
}
}
$Right.Keys | ForEach-Object {
$NewPath = $Path + $_
if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
New-Result $NewPath $Null "=>" $Right[$_]
}
}
}
cls
Compare-Hashtable $Sailings $Flights
side-note: Write Single Records to the Pipeline (SC03), see: Strongly Encouraged Development Guidelines. In other words, don't do the $Results += ... but intermediately put any completed result in the pipeline for the next cmdlet to be picked up (besides, it unnecessarily code)...