Identifying best photos from a photo collection - powershell

I borrowed code from this website and it looks helpful to me as a photographer. Since I do not know PowerShell I would like to ask if this code looks OK. I do not want to lose my photos by mistake.
$source_to_use_as_reference = "C:\photos\mytrip_to_hawai\Best\"
$destination_to_copy = "C:\photos\mytrip_to_hawai\Best\Best_CR2\"
$location_to_find_CR2_files = "C:\photos\mytrip_to_hawai\CR2\"
# these are the codes to find CR2 files that matches with JPG files and copy
# them to new destination
cls
$count_all = 0
$count_matches = 0
$a = Get-ChildItem -Path $source_to_use_as_reference -Recurse -File
foreach ($item in $a) {
$count_all += 1
if ($item.Name -match "JPG") {
$name_as_CR2 = $item.Name.Replace('JPG','CR2')
$location_and_cr2_name = $location_to_find_CR2_files + $name_as_CR2
if (Test-Path -Path $location_and_cr2_name ) {
$destination_and_CR2_name = $destination_to_copy + $name_as_CR2
if (Test-Path -Path $destination_and_CR2_name) {
Write-Output "already exists I skipped ... " $destination_and_CR2_name
} else {
$count_matches += 1
Write-Host "I found it " $destination_and_CR2_name
Copy-Item -Path $location_and_cr2_name -Destination $destination_to_copy
}
} else {
}
}
}
Write-Output "$count_matches matches found and files copied to destination $destination_to_copy"

There's nothing sinister in that script, it simply identifies, counts and copies JPGs and CR2 files to a 2nd location.

Related

Speeding up Test-Path and GCI on large data set

I have a large list of Customer IDs (40,000). Files for each Customer have been saved in many different locations. I first run a Test-Path to see if the directory exists, if it does then I run Get-ChildItem and filter down the results to find the file I need (Any file matching string 'Contract'). I am hoping to educate myself on how to speed this up, I have attempted to introduce another variable 'Trigger' to try and prevent some of the excess code from running if the matching file is found. It is taking a very long time to loop this through 40,000 times so if there is a better way any help greatly appreciated, many thanks.
Here is the code I'm currently using
ForEach ($Customer in $CustomerList)
{
$Trigger = 0
$Result1 = Test-Path "$Location1\$Customer"
$Result2 = Test-Path "$Location2\$Customer"
$Result3 = Test-Path "$Location3\$Customer"
IF ($Result1 -eq $True)
{
$1Files = GCI "$Location1\$Customer" -Recurse
ForEach ($File IN $1Files)
{
IF ($Trigger -eq 0)
{
$FileName = $File.Name
$FileLocation = $File.FullName
IF ($FileName -match 'Contract')
{
$Report += "$FileName $FileLocation"
$Trigger = 1
}
}
}
}
ELSEIF ($Result2 -eq True)
{
Same as result 1 codeblock but using $Location2
}
ELSEIF ($Result3 -eq True)
{
Same as result 1 codeblock but using $Location3
}
}

PowerShell create a duplicate folder with zero-size files

I want to create a 0-filesize mirror image of a set of folder, but while robocopy is really good, it doesn't save all of the information that I would like:
robocopy D:\documents E:\backups\documents_$(Get-Date -format "yyyyMMdd_HHmm")\ /mir /create
The /create switch makes each file in the duplicate folder have zero-size, and that is good, but I would like each file in the duplicate folder to have [size] appended to the end of the name with the size in KB or MB or GB, and the create / last modified time on every file to exactly match the original file. This way, I will have a zero-size duplicate of the folder that I can archive, but which contains all of the relevant information for the files in that directory, showing the size of each and the exact create / last modified times.
Are there good / simple ways to iterate through a tree in PowerShell, and for each item create a zero size file with all relevant information like this?
This would be one way to implement the copy command using the approach I mentioned in the comments. This should give you something to pull ideas from. I didn't intend to spend as much time on it as I did, but I ran it on several directories and found some problems and debugged each problem I encountered. This is a pretty solid example at this point.
function Copy-FolderZeroSizeFiles {
[CmdletBinding()]
param( [Parameter(Mandatory)] [string] $FolderPath,
[Parameter(Mandatory)] [string] $DestinationPath )
$dest = New-Item $DestinationPath -Type Directory -Force
Push-Location -LiteralPath $FolderPath
try {
foreach ($item in Get-ChildItem '.' -Recurse) {
$relPath = Resolve-Path -LiteralPath $item -Relative
$type = if ($item.Attributes -match 'Directory')
{ 'Directory' }
else { 'File' }
$destItem = New-Item "$dest\$relPath" -Type $type -Force
$destItem.Attributes = $item.Attributes
$destItem.LastWriteTime = $item.LastWriteTime
}
} finally {
Pop-Location
}
}
Note: the above implementation is simplistic and represents anything that isn't a directory as a file. That means symbolic links, et al. will be files with no information what they would be linked to.
Here's a function to get the conversion from number of bytes to N.N B/K/M/G format. To get more decimal places, just add 0's to the end of the format strings.
function ConvertTo-FriendlySize($NumBytes) {
switch ($NumBytes) {
{$_ -lt 1024} { "{0,7:0.0}B" -f ($NumBytes) ; break }
{$_ -lt 1048576} { "{0,7:0.0}K" -f ($NumBytes / 1024) ; break }
{$_ -lt 1073741824} { "{0,7:0.0}M" -f ($NumBytes / 1048576) ; break }
default { "{0,7:0.0}G" -f ($NumBytes / 1073741824); break }
}
}
Often, people get these conversions wrong. For instance, it's a common error to use 1024 * 1000 to get Megabytes (which is mixing the base10 value for 1K with the base2 value for 1K) and follow that same logic to get GB and TB.
Here is what I came up with with the additional parts in the question, change $src / $dst as required (D:\VMs is where I keep a lot of Virtual Machines). I have included setting all of CreationTime, LastWriteTime, LastAccessTime so that the backup location with zero-size files is a perfect representation of the source. As I want to use this for archival purposes, I have finally zipped things up and included a date-time stamp in the zipfile name.
# Copy-FolderZeroSizeFiles
$src = "D:\VMs"
$dst = "D:\VMs-Backup"
function ConvertTo-FriendlySize($NumBytes) {
switch ($NumBytes) {
{$_ -lt 1024} { "{0:0.0}B" -f ($NumBytes) ; break } # Change {0: to {0,7: to align to 7 characters
{$_ -lt 1048576} { "{0:0.0}K" -f ($NumBytes / 1024) ; break }
{$_ -lt 1073741824} { "{0:0.0}M" -f ($NumBytes / 1048576) ; break }
default { "{0:0.0}G" -f ($NumBytes / 1073741824); break }
}
}
function Copy-FolderZeroSizeFiles($FolderPath, $DestinationPath) {
Push-Location $FolderPath
if (!(Test-Path $DestinationPath)) { New-Item $DestinationPath -Type Directory }
foreach ($item in Get-ChildItem $FolderPath -Recurse -Force) {
$relPath = Resolve-Path $item.FullName -Relative
if ($item.Attributes -match 'Directory') {
$new = New-Item "$DestinationPath\$relPath" -ItemType Directory -Force -EA Silent
}
else {
$fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($item.Name)
$fileExt = [System.IO.Path]::GetExtension($item.Name)
$fileSize = ConvertTo-FriendlySize($item.Length)
$new = New-Item "$DestinationPath\$(Split-Path $relPath)\$fileBaseName ($fileSize)$fileExt" -ItemType File
}
"$($new.Name) : creation $($item.CreationTime), lastwrite $($item.CreationTime), lastaccess $($item.LastAccessTime)"
$new.CreationTime = $item.CreationTime
$new.LastWriteTime = $item.LastWriteTime
$new.LastAccessTime = $item.LastAccessTime
$new.Attributes = $item.Attributes # Must this after setting creation/write/access times or get error on Read-Only files
}
Pop-Location
}
Copy-FolderZeroSizeFiles $src $dst
$dateTime = Get-Date -Format "yyyyMMdd_HHmm"
$zipName = "$([System.IO.Path]::GetPathRoot($dst))\$([System.IO.Path]::GetFileName($dst))_$dateTime.zip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[IO.Compression.ZipFile]::CreateFromDirectory($dst, $zipName)

Find first available file to output log to

I am working on a complete rewrite of my logging function that I use for a couple hundred scripts and I am trying to make it as robust as possible. I am trying to make it be able to create go through a very basic set of checks to find the first available log that it can write to.
I am trying to write it so it will attempt to write to each log file (in case the files have different permisisons than the directories)
Logic path
Go through each directory in the list
See if there are any logs I can append to
If there is append to them
If not, try to create a new log with # appended to it.
If cannot create a new log, move on to the next directory
This script isn't very difficult, I've written much more complex scripts, but for some reason my brain will not wrap its head around this and I keep coming up with non-robust very repetetive functions and I am trying to keep effiency and speed as the most important priority.
Function TestLogger {
$WriteTee = #{}
$WriteTee.LogName = 'WriteTee.log'
#$WriteTee.LogName = "$(((Split-Path -Leaf $script:MyInvocation.MyCommand.Definition)).BaseName).txt"
$WriteTee.LogPaths = "C:\Windows\",
'C:\Users\1151577373E\Documents\Powershell Scripts\AutoUpdater\',
"$Env:UserProfile"
#"$(Split-Path -Parent $script:MyInvocation.MyCommand.Definition)"
foreach ($Path in $WriteTee.LogPaths) {
$Path = [System.IO.DirectoryInfo]$Path
#Ensure the directory exists and if not, create it.
if (![System.IO.Directory]::Exists($Path)) {
try {
New-Item -Path $Path.Parent.FullName -Name $Path.BaseName -ItemType 'Directory' -ErrorAction Stop -Force | Out-Null
} catch {
continue
}
}
#Check to see if there are any existing logs
$WriteTee.ExistingLogs = Get-ChildItem -LiteralPath $Path -Filter "$(([System.IO.FileInfo]$WriteTee.LogName).BaseName)*$(([System.IO.FileInfo]$WriteTee.LogName).Extension)" |Sort-Object
if ($WriteTee.ExistingLogs.Count -eq 0) {
$WriteTee.LastLogName = $null
} else {
foreach ($ExistingLog in $WriteTee.ExistingLogs) {
try {
[IO.File]::OpenWrite($ExistingLog.FullName).close() | Out-Null
$WriteTee.LogFile = $ExistingLog.FullName
break
} catch {
$WriteTee.LastLogName = $ExistingLog
continue
}
}
}
#If no previous logs can be added to create a new one.
if (-not $WriteTee.ContainsKey('LogFile')) {
switch ($WriteTee.LastLogName.Name) {
{$_ -eq $Null} {
$WriteTee.ExistingLogs.count
Write-Host Create New File
}
{$_ -match '.*\[[0-9]+\]\.'} {
Write-Host AAAAAA
$WriteTee.NextLogName = $WriteTee.NextLogName.FullName.Split('[]')
$WriteTee.NextLogName = $WriteTee.NextLogName[0] + "[" + ([int]($WriteTee.NextLogName[1]) + 1) + "]" + $WriteTee.NextLogName[2]
}
default {}
}
}
#Determine if log file is available or not.
if ($WriteTee.ContainsKey('LogFile')) {
Write-Host "Function Success"
break
} else {
continue
}
}
return $WriteTee.LogFile
}
clear
TestLogger
I think I just burnt myself out yesturday, good night sleep got me going again. here is what I ended up with, really hope someone else finds some use out of it.
Function TestLogger {
$WriteTee = #{}
$WriteTee.LogName = 'WriteTee.log'
#$WriteTee.LogName = "$(((split-path -leaf $Script:MyInvocation.MyCommand.Definition)).BaseName).Log"
$WriteTee.LogPaths = 'C:\Windows\',
"C:\Users\1151577373E\Documents\Powershell Scripts\AutoUpdater\",
"$Env:UserProfile"
#"$(split-path -parent $Script:MyInvocation.MyCommand.Definition)"
Foreach ($Path in $WriteTee.LogPaths) {
If ($WriteTee.ContainsKey('LogFile')) { Break }
$Path = [System.IO.DirectoryInfo]$Path
#Ensure the directory exists and if not, create it.
If (![System.IO.Directory]::Exists($Path)) {
Try {
#Create the directory because .Net will error out if you try to create a file in a directory that doesn't exist yet.
New-Item -Path $Path.Parent.FullName -Name $Path.BaseName -ItemType 'Directory' -ErrorAction Stop -Force |Out-Null
} Catch {
Continue
}
}#End-If
#Check to see if there are any existing logs
$WriteTee.ExistingLogs = Get-ChildItem -LiteralPath $Path -Filter "$(([System.IO.FileInfo]$WriteTee.LogName).BaseName)*$(([System.IO.FileInfo]$WriteTee.LogName).Extension)" |Sort-Object
If ($WriteTee.ExistingLogs.Count -GT 0) {
ForEach ($ExistingLog in $WriteTee.ExistingLogs) {
Try {
[io.file]::OpenWrite($ExistingLog.FullName).close() |Out-Null
$WriteTee.LogFile = $ExistingLog.FullName
break
} Catch {
$WriteTee.LastLogName = $ExistingLog
Continue
}
}
}#End-If
#If no previous logs can be added to create a new one.
switch ($WriteTee.ExistingLogs.Count) {
{$PSItem -EQ 0} {
$WriteTee.TestLogFile = Join-Path -Path $Path -ChildPath $WriteTee.LogName
}
{$PSItem -EQ 1} {
$WriteTee.TestLogFile = Join-Path -Path $Path -ChildPath ($WriteTee.LastLogName.basename + '[0]' + $WriteTee.LastLogName.Extension)
}
{$PSItem -GE 2} {
$WriteTee.TestLogFile = $WriteTee.LastLogName.FullName.Split('[]')
$WriteTee.TestLogFile = ($WriteTee.TestLogFile[0] + '[' + (([int]$WriteTee.TestLogFile[1]) + 1) + ']' + $WriteTee.TestLogFile[2])
}
Default {
Write-Host "If you are looking for an explanation of how you got here, I can tell you I don't have one. But what I do have are a very particular lack of answers that I have aquired over a very long career that make these problems a nightmare for people like me."
Continue
}
}#End-Switch
#Last but not least, try to create the file and hope it is successful.
Try {
[IO.File]::Create($WriteTee.TestLogFile, 1, 'None').close() |Out-Null
$WriteTee.LogFile = $WriteTee.TestLogFile
Break
} Catch {
Continue
}
}#End-ForEach
Return $WriteTee.LogFile
}

Powershell output formatting?

I have a script that scans for a specific folder in users AppData folder. If it finds the folder, it then returns the path to a txt file. So we can see the computer name and username where it was found.
I would like to be able to format the what is actually written to the text file, so it removes everything from the path except the Computer and User names.
Script:
foreach($computer in $computers){
$BetterNet = "\\$computer\c$\users\*\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm"
Get-ChildItem $BetterNet | ForEach-Object {
$count++
$betternetCount++
write-host BetterNet found on: $computer
Add-Content "\\SERVERNAME\PowershellScans\$date\$time\BetterNet.txt" $_`n
write-host
}
}
The text files contain information like this
\\computer-11-1004S10\c$\users\turtle\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
\\computer-1004-24S\c$\users\camel\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
\\computer-1004-23S\c$\users\rabbit\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm
If you have each line in a form of the string $string_containing_path then it is easy to split using split method and then add index(1) and (4) that you need:
$afterSplit = $string_containing_path.Split('\')
$stringThatYouNeed = $afterSplit[1] + " " + $afterSplit[4]
You can also use simple script that will fix your current logs:
$path_in = "C:\temp\list.txt"
$path_out= "C:\temp\output.txt"
$reader = [System.IO.File]::OpenText($path_in)
try {
while($true){
$line = $reader.ReadLine()
if ($line -eq $null) { break }
$line_after_split_method = $line.Split('\')
$stringToOutput = $line_after_split_method[1] + " " + $line_after_split_method[4] + "`r`n"
add-content $path_out $stringToOutput
}
add-content $path_out "End"
}
finally {
$reader.Close()
}
If you split your loop into two foreach loops, one for computer and user directory it would be easier to output the name of the user directory.
$output = foreach($computer in $computers){
$UserDirectories = Get-ChildItem "\\$computer\c$\users\" -Directory
foreach ($Directory in $UserDirectories) {
$BetterNet = Get-ChildItem (Join-Path $Directory.fullname "\AppData\Local\Google\Chrome\User Data\Default\Extensions\gjknjjomckknofjidppipffbpoekiipm")
Add-Content "\\SERVERNAME\PowershellScans\$date\$time\BetterNet.txt" "$computer $($Directory.name)`r`n"
write-host BetterNet found on: $computer
$BetterNet
}
}
$output.count

How can I compare the content of 3 directories in powershell

After looking into this for a while, I've found a lot of people trying to do the same thing but I haven't yet been able to find a complete answer so I'm hoping someone can help us!
What I would like to do is give 2 directories, A and B, and compare everything inside A to everything inside B and if there are any files or folders in B that are not in A then generate an output file detailing the path to the item that is in B and not A. From Here, Id like to use the list of items and say if any of the items in these paths contain something different to the files in 2 directories (the names will be the same so documents\folder\B.txt contains something different to desktop\folder\B.txt) generate another list showing what is different or showing the file path to items that are different if I cant show the text that is different.
I've looked into doing this with .hash but I'm not sure if that's to best way to go about it? something like:
$SourceDocs = Get-ChildItem –Path C:\Documents1 | foreach {Get-FileHash –Path $_.FullName} $DestDocs = Get-ChildItem –Path C:\Documents2 | foreach {Get-FileHash –Path $_.FullName}
In terms of what I have for the physical comparison, I originally was trying to do a 3-way comparison so the code below isn't accurate but its all I've got as yet.
$folderA = 'C:\Users\USERNAME\Desktop\Folder A'
$folderB = 'C:\Users\USERNAME\Desktop\Folder B'
$folderC = 'C:\Users\USERNAME\Desktop\Folder C'
$AChild = Get-ChildItem $folderA -Recurse
$BChild = Get-ChildItem $folderB -Recurse
$CChild = Get-ChildItem $folderC -Recurse
Compare-Object -ReferenceObject $AChild -DifferenceObject $BChild, $CChildcode
After looking into this further, Ive found out it might be better to use 3.0 rather than 2.0 so I can use -Dir more efficiently.
Any questions, let me know.
Any help greatly appreciated.
This is answer to what you want:
What I would like to do is give 2 directories, A and B, and compare everything inside A to everything inside B and if there are any files or folders in B that are not in A then generate an output file detailing the path to the item that is in B and not A.
$folder1 = "C:\Users\USERNAME\Desktop\Folder A"
$folder2 = "C:\Users\USERNAME\Desktop\Folder B"
# Get all files under $folder1, filter out directories
$firstFolder = Get-ChildItem -Recurse $folder1 | Where-Object { -not $_.PsIsContainer }
$failedCount = 0
$i = 0
$totalCount = $firstFolder.Count
$firstFolder | ForEach-Object {
$i = $i + 1
Write-Progress -Activity "Searching Files" -status "Searching File $i of $totalCount" -percentComplete ($i / $firstFolder.Count * 100)
# Check if the file, from $folder1, exists with the same path under $folder2
If ( Test-Path ( $_.FullName.Replace($folder1, $folder2) ) ) {
# Compare the contents of the two files...
If ( Compare-Object (Get-Content $_.FullName) (Get-Content $_.FullName.Replace($folder1, $folder2) ) ) {
# List the paths of the files containing diffs
$fileSuffix = $_.FullName.TrimStart($folder1)
$failedCount = $failedCount + 1
Write-Host "$fileSuffix is on each server, but does not match"
}
}
else
{
$fileSuffix = $_.FullName.TrimStart($folder1)
$failedCount = $failedCount + 1
Write-Host "$fileSuffix is only in folder 1"
$fileSuffix | Out-File "$env:userprofile\desktop\folder1.txt" -Append
}
}
$secondFolder = Get-ChildItem -Recurse $folder2 | Where-Object { -not $_.PsIsContainer }
$i = 0
$totalCount = $secondFolder.Count
$secondFolder | ForEach-Object {
$i = $i + 1
Write-Progress -Activity "Searching for files only on second folder" -status "Searching File $i of $totalCount" -percentComplete ($i / $secondFolder.Count * 100)
# Check if the file, from $folder2, exists with the same path under $folder1
If (!(Test-Path($_.FullName.Replace($folder2, $folder1))))
{
$fileSuffix = $_.FullName.TrimStart($folder2)
$failedCount = $failedCount + 1
Write-Host "$fileSuffix is only in folder 2"
$fileSuffix | Out-File "$env:userprofile\desktop\folder2.txt" -Append
}
}