I have a folder with 1 or more files like this:
bigfileA.txt
bigfileB.txt
bigfileC.txt
Each file contains 1 line and is delimited by '
For example, bigfileA.txt may contain abcdef'ghijklmnop'qrst
How can I use powershell so that when I run the .ps file it will automatically split all files in the specified directory into multiple files?
The output will look like this:
bigfileA1.txt --> abcdef
bigfileA2.txt --> ghijklmnop
bigfileA3.txt --> qrst
bigfileB1.txt --> uvw
bigfileB2.txt --> xyz
bigfileC1.txt --> ... and so on...
... and so on...
Can someone help? Thank you in advance!
The below script will go through a directory you choose and create a text file with the name of the base file + the current index (For example bigfileA + 1) and will have the delimited value inside.
$bigfiles = Get-ChildItem -Path 'C:\path\to\bigfiles\here'
ForEach ($path in $bigfiles)
{
$contents = Get-Content $path.FullName
ForEach ($line in $contents)
{
$splitItems = $line.split("'")
For ($i = 0; $i -lt $splitItems.count; $i++)
{
New-Item -Path "$($path.Directory)\$($path.BaseName)$i.txt" -ItemType File -Value $splitItems[$i]
}
}
}
EDIT: Re-read question, must have misinterpreted the first time. Updated code.
If you want the files to start at 1 instead of 0 change this line
New-Item -Path "$($path.Directory)\$($path.BaseName)$i.txt" -ItemType File -Value $splitItems[$i]
to this
New-Item -Path "$($path.Directory)\$($path.BaseName)$($i+1).txt" -ItemType File -Value $splitItems[$i]
We're simply changing the $i to $($i+1)
Functional Edit
Has not been tested and was put together quickly because i'm at work but it's at least a start :)
function Split-BigFiles
{
[CmdletBinding(DefaultParameterSetName='All',
SupportsShouldProcess=$false,
PositionalBinding=$false)]
Param
(
# Paths to bigfiles
[Parameter(Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false,
Position=0,
ParameterSetName='All')]
[ValidateScript({Test-Path $_ -PathType Container})]
[ValidateNotNullOrEmpty()]
[String[]]
$Path
)
Begin
{
Write-Verbose "Starting Function Split-BigFiles"
}
Process
{
ForEach($folder in $Path)
{
Try
{
$bigfiles = Get-ChildItem -Path $folder -ErrorAction Stop
}
Catch
{
Write-Warning "Oh no i couldn't get the folder! This is the error i got $($_.Exception.Message)"
}
if ($bigfiles)
{
ForEach ($path in $bigfiles)
{
$contents = Get-Content $path.FullName
ForEach ($line in $contents)
{
$splitItems = $line.split("'")
For ($i = 0; $i -lt $splitItems.count; $i++)
{
New-Item -Path "$($path.Directory)\$($path.BaseName)$i.txt" -ItemType File -Value $splitItems[$i]
}
}
}
}
}
}
End
{
Write-Verbose "Exiting Function Split-BigFiles"
}
}
We have a backup that runs every other day, but the files are large and we want to just remove every other one once we get a certain amount of backup files with our file signature.
I've tried this:
$Drive = "E:\temp\"
$deleteTime = -42;
$limit = (Get-Date).AddDays($deleteTime)
#this is finding the correct files but I don't think it's really in an array
$temp1 = Get-ChildItem -Path $Drive -filter "*junk.vhd*" | Where-Object {$_.LastWriteTime -lt $limit} | Select -Expand Name
for($i=$temp1.GetLowerBound(0); $i -le $temp1.GetUpperBound(0); $i+=2) {
Write-Host "removing $temp1[$i]" #this is listing the entire array with a [0] for first one and the third [2] element also, whether I cast to an array or not
}
I tried this instead of the above (Get-ChildItem) line currently but it listed the entire set of junk files for [0] instead of just the first junk.vhd at [0]:
[array]$temp1 =#( Get-ChildItem -Path $Drive -filter "*junk.vhd*" | Where-Object {$_.LastWriteTime -lt $limit} | Foreach-Object {$_.Name} )
I tried this too:
$limit = (Get-Date).AddDays(-42)
$list = (dir -Filter *junk.ps1 | where LastWriteTime -lt $limit).FullName
$count = $list.Length
for ($i = 0; $i -lt $count; $i += 2)
{
Write-Verbose "[$i] $($list[$i])"
#it's not getting in here because I'm not sure how
#to add the $Drive location and list is empty
}
Does anyone have a suggestion how to get an array of the filenames from $Drive location with the signature *junk.vhd so I can loop through them and remove every other one?
An internet search isn't turning much up.
This works for me:
$deleteTime = -12;
$limit = (Get-Date).AddDays($deleteTime)
$t = Get-ChildItem -Path $pwd -filter "p*.txt" | Where-Object {$_.LastWriteTime -lt $limit} | Select -Expand Name
foreach ($a in $t) { Write-Host "Name : $a" }
What have I missed from what you were looking for?
(Obviously, you will need to maintain a counter and do some modulo arithmetic in the body of the foreach() statement... )
This works, too:
for($i=$t.GetLowerBound(0); $i -le $t.GetUpperBound(0); $i+=2) {
$n = $t[$i]
Write-Host "removing $n"
}
I have a root directory that consists of many folders and sub folders. I need to check whether a particular file like *.sln or *.designer.vb exists in the folders or subfolders and output the result in a text file.
For Eg:
$root = "C:\Root\"
$FileType = ".sln",".designer.vb"
the text file will have result somewhat like below:
.sln ---> 2 files
.sln files path ---->
c:\Root\Application1\subfolder1\Test.sln
c:\Root\Application2\subfolder1\Test2.sln
Any help will be highly appreciated!
Regards,
Ashish
Try this:
function Get-ExtensionCount {
param(
$Root = "C:\Root\",
$FileType = #(".sln", ".designer.vb"),
$Outfile = "C:\Root\rootext.txt"
)
$output = #()
Foreach ($type in $FileType) {
$files = Get-ChildItem $Root -Filter *$type -Recurse | ? { !$_.PSIsContainer }
$output += "$type ---> $($files.Count) files"
foreach ($file in $files) {
$output += $file.FullName
}
}
$output | Set-Content $Outfile
}
I turned it into a function with your values as default parameter-values. Call it by using
Get-ExtensionCount #for default values
Or
Get-ExtensionCount -Root "d:\test" -FileType ".txt", ".bmp" -Outfile "D:\output.txt"
Output saved to the file ex:
.txt ---> 3 files
D:\Test\as.txt
D:\Test\ddddd.txt
D:\Test\sss.txt
.bmp ---> 2 files
D:\Test\dsadsa.bmp
D:\Test\New Bitmap Image.bmp
To get the all the filecounts at the start, try:
function Get-ExtensionCount {
param(
$Root = "C:\Root\",
$FileType = #(".sln", ".designer.vb"),
$Outfile = "C:\Root\rootext.txt"
)
#Filecount per type
$header = #()
#All the filepaths
$filelist = #()
Foreach ($type in $FileType) {
$files = Get-ChildItem $Root -Filter *$type -Recurse | ? { !$_.PSIsContainer }
$header += "$type ---> $($files.Count) files"
foreach ($file in $files) {
$filelist += $file.FullName
}
}
#Collect to single output
$output = #($header, $filelist)
$output | Set-Content $Outfile
}
Here's a one-liner to determine if at least one file with extension .txt or .ps1 exists in the directory $OutputPath:
(Get-ChildItem -Path $OutputPath -force | Where-Object Extension -in ('.txt','.ps1') | Measure-Object).Count
Explanation: the command tells you the number of files in the specified directory matching any of the listed extensions. You can append -ne 0 to the end, which returns true or false to be used in an if block.
This will search the directory $root and its subdirectories for files of type $FileType, including hidden files and excluding directories:
$root = "C:\Root\";
$FileType = "*.sln", "*.designer.vb";
$results = Get-ChildItem -Path $root -Force -Recurse `
| Where-Object {
if ($_ -isnot [System.IO.DirectoryInfo])
{
foreach ($pattern in $FileType)
{
if ($_.Name -like $pattern)
{
return $true;
}
}
}
return $false;
}
Note that I've modified the strings in $FileType to be formatted as a wildcard pattern. Then group the files by extension:
$resultGroups = $results | Group-Object -Property 'Extension';
Then loop through each group and print the file count and paths:
foreach ($group in $resultGroups)
{
# $group.Count: The number of files with that extension
# $group.Group: A collection of FileInfo objects
# $group.Name: The file extension with leading period
Write-Host "$($group.Name) ---> $($group.Count) files";
Write-Host "$($group.Name) files path ---->";
foreach ($file in $group.Group)
{
Write-Host $file.FullName;
}
}
To write the results to a file instead of the console, use the Out-File cmdlet instead of the Write-Host cmdlet.
I'm working on a PowerShell script that finds all the files with PATTERN within a given DIRECTORY, prints out the relevant lines of the document with the PATTERN highlighted, and then replaces the PATTERN with a provided REPLACE word, then saves the file back. So it actually edits the file.
Except I can't get it to alter the file, because Windows complains about the file already being open. I tried several methods to solve this, but keep running into the issue. Perhaps someone can help:
param(
[string] $pattern = ""
,[string] $replace = ""
,[string] $directory ="."
,[switch] $recurse = $false
,[switch] $caseSensitive = $false)
if($pattern -eq $null -or $pattern -eq "")
{
Write-Error "Please provide a search pattern." ; return
}
if($directory -eq $null -or $directory -eq "")
{
Write-Error "Please provide a directory." ; return
}
if($replace -eq $null -or $replace -eq "")
{
Write-Error "Please provide a string to replace." ; return
}
$regexPattern = $pattern
if($caseSensitive -eq $false) { $regexPattern = "(?i)$regexPattern" }
$regex = New-Object System.Text.RegularExpressions.Regex $regexPattern
function Write-HostAndHighlightPattern([string] $inputText)
{
$index = 0
$length = $inputText.Length
while($index -lt $length)
{
$match = $regex.Match($inputText, $index)
if($match.Success -and $match.Length -gt 0)
{
Write-Host $inputText.SubString($index, $match.Index) -nonewline
Write-Host $match.Value.ToString() -ForegroundColor Red -nonewline
$index = $match.Index + $match.Length
}
else
{
Write-Host $inputText.SubString($index) -nonewline
$index = $inputText.Length
}
}
}
Get-ChildItem $directory -recurse:$recurse |
Select-String -caseSensitive:$caseSensitive -pattern:$pattern |
foreach {
$file = ($directory + $_.FileName)
Write-Host "$($_.FileName)($($_.LineNumber)): " -nonewline
Write-HostAndHighlightPattern $_.Line
%{ Set-Content $file ((Get-Content $file) -replace ([Regex]::Escape("[$pattern]")),"[$replace]")}
Write-Host "`n"
Write-Host "Processed: $($file)"
}
The issue is located within the final block of code, right at the Get-ChildItem call. Of course, some of the code in that block is now a bit mangled due to me trying to fix the problem then stopping, but keep in mind the intent of that part of the script. I want to get the content, replace the words, then save the altered text back to the file I got it from.
Any help at all would be greatly appreciated.
Removed my previous answer, replacing it with this:
Get-ChildItem $directory -recurse:$recurse
foreach {
$file = ($directory + $_.FileName)
(Get-Content $file) | Foreach-object {
$_ -replace ([Regex]::Escape("[$pattern]")),"[$replace]")
} | Set-Content $file
}
Note:
The parentheses around Get-Content to ensure the file is slurped in one go (and therefore closed).
The piping to subsequent commands rather than inlining.
Some of your commands have been removed to ensure it's a simple test.
Just a suggestion but you might try looking at the documentation for the parameters code block. There is a more efficient way to ensure that a parameter is entered if you require it and to throw an error message if the user doesn't.
About_throw: http://technet.microsoft.com/en-us/library/dd819510.aspx
About_functions_advanced_parameters: http://technet.microsoft.com/en-us/library/dd347600.aspx
And then about using Write-Host all the time: http://powershell.com/cs/blogs/donjones/archive/2012/04/06/2012-scripting-games-commentary-stop-using-write-host.aspx
Alright, I finally sat down and just typed everything sequentially in PowerShell, then used that to make my script.
It was actually really simple;
$items = Get-ChildItem $directory -recurse:$recurse
$items |
foreach {
$file = $_.FullName
$content = get-content $file
$newContent = $content -replace $pattern, $replace
Set-Content $file $newcontent
}
Thanks for all your help guys.
I have two different folders with xml files. One folder (folder2) contains updated and new xml files compared to the other (folder1). I need to know which files in folder2 are new/updated compared to folder1 and copy them to a third folder (folder3). What's the best way to accomplish this in PowerShell?
OK, I'm not going to code the whole thing for you (what's the fun in that?) but I'll get you started.
First, there are two ways to do the content comparison. The lazy/mostly right way, which is comparing the length of the files; and the accurate but more involved way, which is comparing a hash of the contents of each file.
For simplicity sake, let's do the easy way and compare file size.
Basically, you want two objects that represent the source and target folders:
$Folder1 = Get-childitem "C:\Folder1"
$Folder2 = Get-childitem "C:\Folder2"
Then you can use Compare-Object to see which items are different...
Compare-Object $Folder1 $Folder2 -Property Name, Length
which will list for you everything that is different by comparing only name and length of the file objects in each collection.
You can pipe that to a Where-Object filter to pick stuff that is different on the left side...
Compare-Object $Folder1 $Folder2 -Property Name, Length | Where-Object {$_.SideIndicator -eq "<="}
And then pipe that to a ForEach-Object to copy where you want:
Compare-Object $Folder1 $Folder2 -Property Name, Length | Where-Object {$_.SideIndicator -eq "<="} | ForEach-Object {
Copy-Item "C:\Folder1\$($_.name)" -Destination "C:\Folder3" -Force
}
Recursive Directory Diff Using MD5 Hashing (Compares Content)
Here is a pure PowerShell v3+ recursive file diff (no dependencies) that calculates MD5 hash for each directories file contents (left/right). Can optionally export CSV's along with a summary text file. Default outputs results to stdout. Can either drop the rdiff.ps1 file into your path or copy the contents into your script.
USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir]
Here is the gist. Recommended to use version from gist as it may have additional features over time. Feel free to send pull requests.
#########################################################################
### USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir] ###
### ADD LOCATION OF THIS SCRIPT TO PATH ###
#########################################################################
[CmdletBinding()]
param (
[parameter(HelpMessage="Stores the execution working directory.")]
[string]$ExecutionDirectory=$PWD,
[parameter(Position=0,HelpMessage="Compare two directories recursively for differences.")]
[alias("c")]
[string[]]$Compare,
[parameter(HelpMessage="Export a summary to path.")]
[alias("s")]
[string]$ExportSummary
)
### FUNCTION DEFINITIONS ###
# SETS WORKING DIRECTORY FOR .NET #
function SetWorkDir($PathName, $TestPath) {
$AbsPath = NormalizePath $PathName $TestPath
Set-Location $AbsPath
[System.IO.Directory]::SetCurrentDirectory($AbsPath)
}
# RESTORES THE EXECUTION WORKING DIRECTORY AND EXITS #
function SafeExit() {
SetWorkDir /path/to/execution/directory $ExecutionDirectory
Exit
}
function Print {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Message to print.")]
[string]$Message,
[parameter(HelpMessage="Specifies a success.")]
[alias("s")]
[switch]$SuccessFlag,
[parameter(HelpMessage="Specifies a warning.")]
[alias("w")]
[switch]$WarningFlag,
[parameter(HelpMessage="Specifies an error.")]
[alias("e")]
[switch]$ErrorFlag,
[parameter(HelpMessage="Specifies a fatal error.")]
[alias("f")]
[switch]$FatalFlag,
[parameter(HelpMessage="Specifies a info message.")]
[alias("i")]
[switch]$InfoFlag = !$SuccessFlag -and !$WarningFlag -and !$ErrorFlag -and !$FatalFlag,
[parameter(HelpMessage="Specifies blank lines to print before.")]
[alias("b")]
[int]$LinesBefore=0,
[parameter(HelpMessage="Specifies blank lines to print after.")]
[alias("a")]
[int]$LinesAfter=0,
[parameter(HelpMessage="Specifies if program should exit.")]
[alias("x")]
[switch]$ExitAfter
)
PROCESS {
if($LinesBefore -ne 0) {
foreach($i in 0..$LinesBefore) { Write-Host "" }
}
if($InfoFlag) { Write-Host "$Message" }
if($SuccessFlag) { Write-Host "$Message" -ForegroundColor "Green" }
if($WarningFlag) { Write-Host "$Message" -ForegroundColor "Orange" }
if($ErrorFlag) { Write-Host "$Message" -ForegroundColor "Red" }
if($FatalFlag) { Write-Host "$Message" -ForegroundColor "Red" -BackgroundColor "Black" }
if($LinesAfter -ne 0) {
foreach($i in 0..$LinesAfter) { Write-Host "" }
}
if($ExitAfter) { SafeExit }
}
}
# VALIDATES STRING MIGHT BE A PATH #
function ValidatePath($PathName, $TestPath) {
If([string]::IsNullOrWhiteSpace($TestPath)) {
Print -x -f "$PathName is not a path"
}
}
# NORMALIZES RELATIVE OR ABSOLUTE PATH TO ABSOLUTE PATH #
function NormalizePath($PathName, $TestPath) {
ValidatePath "$PathName" "$TestPath"
$TestPath = [System.IO.Path]::Combine((pwd).Path, $TestPath)
$NormalizedPath = [System.IO.Path]::GetFullPath($TestPath)
return $NormalizedPath
}
# VALIDATES STRING MIGHT BE A PATH AND RETURNS ABSOLUTE PATH #
function ResolvePath($PathName, $TestPath) {
ValidatePath "$PathName" "$TestPath"
$ResolvedPath = NormalizePath $PathName $TestPath
return $ResolvedPath
}
# VALIDATES STRING RESOLVES TO A PATH AND RETURNS ABSOLUTE PATH #
function RequirePath($PathName, $TestPath, $PathType) {
ValidatePath $PathName $TestPath
If(!(Test-Path $TestPath -PathType $PathType)) {
Print -x -f "$PathName ($TestPath) does not exist as a $PathType"
}
$ResolvedPath = Resolve-Path $TestPath
return $ResolvedPath
}
# Like mkdir -p -> creates a directory recursively if it doesn't exist #
function MakeDirP {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path create.")]
[string]$Path
)
PROCESS {
New-Item -path $Path -itemtype Directory -force | Out-Null
}
}
# GETS ALL FILES IN A PATH RECURSIVELY #
function GetFiles {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get files for.")]
[string]$Path
)
PROCESS {
ls $Path -r | where { !$_.PSIsContainer }
}
}
# GETS ALL FILES WITH CALCULATED HASH PROPERTY RELATIVE TO A ROOT DIRECTORY RECURSIVELY #
# RETURNS LIST OF #{RelativePath, Hash, FullName}
function GetFilesWithHash {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get directories for.")]
[string]$Path,
[parameter(HelpMessage="The hash algorithm to use.")]
[string]$Algorithm="MD5"
)
PROCESS {
$OriginalPath = $PWD
SetWorkDir path/to/diff $Path
GetFiles $Path | select #{N="RelativePath";E={$_.FullName | Resolve-Path -Relative}},
#{N="Hash";E={(Get-FileHash $_.FullName -Algorithm $Algorithm | select Hash).Hash}},
FullName
SetWorkDir path/to/original $OriginalPath
}
}
# COMPARE TWO DIRECTORIES RECURSIVELY #
# RETURNS LIST OF #{RelativePath, Hash, FullName}
function DiffDirectories {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Directory to compare left.")]
[alias("l")]
[string]$LeftPath,
[parameter(Mandatory=$TRUE,Position=1,HelpMessage="Directory to compare right.")]
[alias("r")]
[string]$RightPath
)
PROCESS {
$LeftHash = GetFilesWithHash $LeftPath
$RightHash = GetFilesWithHash $RightPath
diff -ReferenceObject $LeftHash -DifferenceObject $RightHash -Property RelativePath,Hash
}
}
### END FUNCTION DEFINITIONS ###
### PROGRAM LOGIC ###
if($Compare.length -ne 2) {
Print -x "Compare requires passing exactly 2 path parameters separated by comma, you passed $($Compare.length)." -f
}
Print "Comparing $($Compare[0]) to $($Compare[1])..." -a 1
$LeftPath = RequirePath path/to/left $Compare[0] container
$RightPath = RequirePath path/to/right $Compare[1] container
$Diff = DiffDirectories $LeftPath $RightPath
$LeftDiff = $Diff | where {$_.SideIndicator -eq "<="} | select RelativePath,Hash
$RightDiff = $Diff | where {$_.SideIndicator -eq "=>"} | select RelativePath,Hash
if($ExportSummary) {
$ExportSummary = ResolvePath path/to/summary/dir $ExportSummary
MakeDirP $ExportSummary
$SummaryPath = Join-Path $ExportSummary summary.txt
$LeftCsvPath = Join-Path $ExportSummary left.csv
$RightCsvPath = Join-Path $ExportSummary right.csv
$LeftMeasure = $LeftDiff | measure
$RightMeasure = $RightDiff | measure
"== DIFF SUMMARY ==" > $SummaryPath
"" >> $SummaryPath
"-- DIRECTORIES --" >> $SummaryPath
"`tLEFT -> $LeftPath" >> $SummaryPath
"`tRIGHT -> $RightPath" >> $SummaryPath
"" >> $SummaryPath
"-- DIFF COUNT --" >> $SummaryPath
"`tLEFT -> $($LeftMeasure.Count)" >> $SummaryPath
"`tRIGHT -> $($RightMeasure.Count)" >> $SummaryPath
"" >> $SummaryPath
$Diff | Format-Table >> $SummaryPath
$LeftDiff | Export-Csv $LeftCsvPath -f
$RightDiff | Export-Csv $RightCsvPath -f
}
$Diff
SafeExit
Further to #JNK's answer, you might want to ensure that you are always working with files rather than the less-intuitive output from Compare-Object. You just need to use the -PassThru switch...
$Folder1 = Get-ChildItem "C:\Folder1"
$Folder2 = Get-ChildItem "C:\Folder2"
$Folder2 = "C:\Folder3\"
# Get all differences, i.e. from both "sides"
$AllDiffs = Compare-Object $Folder1 $Folder2 -Property Name,Length -PassThru
# Filter for new/updated files from $Folder2
$Changes = $AllDiffs | Where-Object {$_.Directory.Fullname -eq $Folder2}
# Copy to $Folder3
$Changes | Copy-Item -Destination $Folder3
This at least means you don't have to worry about which way the SideIndicator arrow points!
Also, bear in mind that you might want to compare on LastWriteTime as well.
Sub-folders
Looping through the sub-folders recursively is a little more complicated as you probably will need to strip off the respective root folder paths from the FullName field before comparing lists.
You could do this by adding a new ScriptProperty to your Folder1 and Folder2 lists:
$Folder1 | Add-Member -MemberType ScriptProperty -Name "RelativePath" `
-Value {$this.FullName -replace [Regex]::Escape("C:\Folder1"),""}
$Folder2 | Add-Member -MemberType ScriptProperty -Name "RelativePath" `
-Value {$this.FullName -replace [Regex]::Escape("C:\Folder2"),""}
You should then be able to use RelativePath as a property when comparing the two objects and also use that to join on to "C:\Folder3" when copying to keep the folder structure in place.
Here's an approach which will find files which are missing or differ in content.
First, a quick-and-dirty one-liner (see caveat below).
dir -r | rvpa -Relative |%{ if (Test-Path $right\$_) { if (Test-Path -Type Leaf $_) { if ( diff (cat $_) (cat $right\$_ ) ) { $_ } } } else { $_ } }
Run the above in one of the directories, with $right set to (or replaced with) the path to the other directory. Things missing from $right, or which differ in content, will be reported. No output means no differences found. CAVEAT: Things existing in $right but missing from the left will not be found/reported.
This doesn't bother calculating hashes; it just compares the file contents directly. Hashing makes sense when you want to reference something in another context (later date, on another machine, etc.), but when we're comparing things directly, it adds nothing but overhead. (It's also theoretically possible for two files to have the same hash, although that's basically impossible to happen by accident. Deliberate attack, on the other hand...)
Here's a more proper script, which handles more corner cases and errors.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)][string]$Left,
[Parameter(Mandatory=$True,Position=1)][string]$Right
)
# throw errors on undefined variables
Set-StrictMode -Version 1
# stop immediately on error
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
# init counters
$Items = $MissingRight = $MissingLeft = $Contentdiff = 0
# make sure the given parameters are valid paths
$left = Resolve-Path $left
$right = Resolve-Path $right
# make sure the given parameters are directories
if (-Not (Test-Path -Type Container $left)) { throw "not a container: $left" }
if (-Not (Test-Path -Type Container $right)) { throw "not a container: $right" }
# Starting from $left as relative root, walk the tree and compare to $right.
Push-Location $left
try {
Get-ChildItem -Recurse | Resolve-Path -Relative | ForEach-Object {
$rel = $_
$Items++
# make sure counterpart exists on the other side
if (-not (Test-Path $right\$rel)) {
Write-Output "missing from right: $rel"
$MissingRight++
return
}
# compare contents for files (directories just have to exist)
if (Test-Path -Type Leaf $rel) {
if ( Compare-Object (Get-Content $left\$rel) (Get-Content $right\$rel) ) {
Write-Output "content differs : $rel"
$ContentDiff++
}
}
}
}
finally {
Pop-Location
}
# Check items in $right for counterparts in $left.
# Something missing from $left of course won't be found when walking $left.
# Don't need to check content again here.
Push-Location $right
try {
Get-ChildItem -Recurse | Resolve-Path -Relative | ForEach-Object {
$rel = $_
if (-not (Test-Path $left\$rel)) {
Write-Output "missing from left : $rel"
$MissingLeft++
return
}
}
}
finally {
Pop-Location
}
Write-Verbose "$Items items, $ContentDiff differed, $MissingLeft missing from left, $MissingRight from right"
Handy version using script parameter
Simple file-level comparasion
Call it like PS > .\DirDiff.ps1 -a .\Old\ -b .\New\
Param(
[string]$a,
[string]$b
)
$fsa = Get-ChildItem -Recurse -path $a
$fsb = Get-ChildItem -Recurse -path $b
Compare-Object -Referenceobject $fsa -DifferenceObject $fsb
Possible output:
InputObject SideIndicator
----------- -------------
appsettings.Development.json <=
appsettings.Testing.json <=
Server.pdb =>
ServerClientLibrary.pdb =>
Do this:
compare (Get-ChildItem D:\MyFolder\NewFolder) (Get-ChildItem \\RemoteServer\MyFolder\NewFolder)
And even recursively:
compare (Get-ChildItem -r D:\MyFolder\NewFolder) (Get-ChildItem -r \\RemoteServer\MyFolder\NewFolder)
and is even hard to forget :)
gci -path 'C:\Folder' -recurse |where{$_.PSIsContainer}
-recurse will explore all subtrees below the root path given and the .PSIsContainer property is the one you want to test for to grab all folders only. You can use where{!$_.PSIsContainer} for just files.