Renaming Folders ascending - powershell

I want to automate my backups and Keep always some old versions. The idea was to use Windows Backup on a share and use a PowerShell script to start it.
I'm almost done, but I stuck at the renaming.
Share: \\Server\SysBackup\WindowsImageBackup
in that share there are Folders for all PC's I have. So for example I want to Keep the three last backups, it should to the following:
Current Backup: PC1
Old Backups: PC1_1, PC1_2
Now I want to rename them to one higher number
PC1_2 → PC1_3
PC1_1 → PC1_2
PC1 → PC1_1
So the backup now can use the PC1 Folder for the newest backup.
That's what I tried so far:
$BackupFolder = Get-ChildItem -Directory ($Target + "\WindowsImageBackup") |
Where-Object -Property Name -Like $env:computername* |
Select-Object -Property Name
$CurrentBackups = $BackupFolder.Count
if ($CurrentBackups -ge 1) {
Push-Location ($Target + "\WindowsImageBackup")
$i = 0
$xCurrentArray = $CurrentBackups - 1
$NewSubVersion = $CurrentBackups
while ($i -le $CurrentBackups) {
$NewName = $BackupFolder[$xCurrentArray].Name.TrimEnd("_")
Rename-Item $BackupFolder[$xCurrentArray] -NewName
}
Pop-Location
Clear-Variable $i
}
The files are not renamed, and I'm getting the following error:
You cannot call a method on a null-valued expression.
Rename-Item : Missing an argument for parameter 'NewName'. Specify a parameter of type 'System.String' and try again.
Where is my mistake?

I found the error
if ($CurrentBackups -ge 1)
{
Push-Location ($Target + "\WindowsImageBackup\")
$i= 0
$xCurrentArray = $CurrentBackups - 1
$NewSubVersion = $CurrentBackups
while ($i -lt $CurrentBackups)
{
if ($BackupFolder[$xCurrentArray].Contains("_"))
{
$Index = $BackupFolder[$xCurrentArray].IndexOf("_")
$NewName = $BackupFolder[$xCurrentArray].Substring(0, $Index)
Rename-Item $BackupFolder[$xCurrentArray] -NewName ($NewName + "_" + $NewSubVersion)
Clear-Variable Index
$NewSubVersion--
}
else
{
$NewName = $BackupFolder[$xCurrentArray]
Rename-Item $BackupFolder[$xCurrentArray] -NewName ($NewName + "_1")
}
$i++
$xCurrentArray--
}
Pop-Location
Clear-Variable i
}

Related

Powershell adding file sizes, unexpected result

I am currently working on a powershell script to copy a random selection of songs from my NAS onto an SD card. As an added complication, I can have no more than 512 songs per folder and also obviously, need to stop the process before I run out of free space on the card.
I have written a nearly complete script (with reduced amounts of songs for testing purposes), but am struggling with keeping track of the total size of the files I have copied. As an example, a test run with a total of 112MB of files gives a recorded value (in $copied_size) of 1245. I don't know what that value means, it doesn't seem to be a realistic value of GB, Gb, MB or Mb. I am obviously missing something here. Any ideas?
Here is the script, I haven't put in the size restriction yet:
$j = 1
$i = 0
$files_per_folder = 5
$sd_card_size = 15920000000
$copied_size = 0
$my_path = '\\WDMYCLOUD\Public\Shared Music'
$random = Get-Random -Count 100 -InputObject (1..200)
For ($j=1; $j -le 5; $j++)
{
md ("F:\" + $j)
$list = Get-ChildItem -Path $my_path | ?{$_.PSIsContainer -eq $false -and $_.Extension -eq '.mp3'}
For ($i=0; $i -le $files_per_folder - 1; $i++)
{
Copy-Item -Path ($my_path + "\" + $list[$random[(($J - 1) * $files_per_folder) +$i]]) -Destination ('F:\' + $j)
$copied_size = $copied_size + ($my_path + "\" + $list[$random[(($J - 1) * $files_per_folder) +$i]]).length
}
}
Write-Host "Copied Size = " $copied_size
Here's a way to solve your problem using some more powershell-like patterns. It compares the current file to be copied with space remaining and will exit out of the top-level loop if that condition is true.
#requires -Version 3
$path = '\\share\Public\Shared Music'
$filesPerFolder = 5
$copySize = 0
$random = 1..200 | Get-Random -Count 100
$files = Get-ChildItem -Path $path -File -Filter *.mp3
:main
for ($i = 1; $i -le 5; $i++) {
$dest = New-Item -Path F:\$i -ItemType Directory -Force
for ($j = 0; $j -le $filesPerFolder; $j++) {
$file = $files[$random[(($j - 1) * $filesPerFolder) + $i]]
if ((Get-PSDrive -Name F).Free -lt $file.Length) {
break main
}
$file | Copy-Item -Destination $dest\
$copySize += $file.Length
}
}

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
}
}

Powershell incorrect parameter

So I am trying to pass the file directory to the function convert at the bottom. When I run the script I receive the output:
Succes
C:\test 2\00000027627-00001\PROCESSING CHECKLIST
convert : Invalid Parameter - 2\00000027627-00001\PROCESSING
At C:\Users\pmanca\Desktop\msbxmlreader.ps1:35 char:13
+ convert([string]$hostdirectoryPlusLoanPlusDocType)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (Invalid Paramet...0001\PROCESSING:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
It appears to be cutting off the file path after C:\test. Could the file path be too long? even so i wouldn't imagine getting the error at the function call and instead somewhere in the function when it couldn't resolve the path.
#? is a shortcut for where object and % is a shortcut for foreach-object
cls
#count 1 = Loan Number
#count 4 = Cust Num
#count 5 = Document Type
$hostdirectory = "C:\test 2"
$count = 0
$file = (Select-xml -Path "$hostdirectory\index.xml" -XPath / ).Node
$test = $file.ExportedResult.Docs.ExportedDoc.Doc.UdiValues.UdiValue.Value# | Select-Object {$_.UdiValue.Value.InnerXML} -Unique #|? {$_.UdiValue.Name -eq "Loan Number"}
$test | ForEach-Object{
$count++
# Write-Host $_.innerxml "----" $count
if($count -eq 1){
[string]$xmlHold = $_.InnerXML
$hostdirectoryPlusLoan = "$hostdirectory\$xmlHold"
if(!(test-path $hostdirectoryPlusLoan)){
New-Item $hostdirectoryPlusLoan -ItemType directory
}
}
if($count -eq 5){
[string]$xmlHold = $_.InnerXML
$hostdirectoryPlusLoanPlusDocType = "$hostdirectoryPlusLoan\$xmlHold"
if(!(test-path $hostdirectoryPlusLoanPlusDocType)){
New-Item $hostdirectoryPlusLoanPlusDocType -ItemType directory
}
if(Test-Path "$hostdirectory\$xmlhold.pdf"){
$check = Copy-Item "$hostdirectory\$xmlHold.pdf" -Destination $hostdirectoryPlusLoanPlusDocType -ErrorAction SilentlyContinue
if(-not $?) {write-warning "Copy Failed"; Write-Host $Error[0].exception.message}
else {write-host "Succes"}
Write-Host $hostdirectoryPlusLoanPlusDocType
convert([string]$hostdirectoryPlusLoanPlusDocType)
}
}
if($count -ge 8){
$count = 0
# Write-Host "-----------------------------------"
}
}
function convert([string]$inputDirectory){
write-host $inputDirectory
#Variable to hold current input directory
$InputPathFilter = $InputDirectory + '\*.pdf'
#Variable to hold the list of PDFs from the current input directory
$PDFList = #(gci $InputPathFilter | foreach {write-output $_.name})
#Loop through list of PDF files to convert to TIF image files.
for ($j=0; $j -lt $PDFList.count; $j++) {
#Each PDF will go into its own directory/batch
#Create a variable with only the file name
$FileName = $PDFList[$j] -replace(".pdf",'')
#Variable of the full path to the current PDF
$InputPath = $InputDirectory + '\' + $PDFList[$j]
#Variable to hold output path of each TIF file. Filename format is that used by batches in ScerIS (i.e. 00010001.tif, 00010002.tif, etc...)
$OutputFullDirectory = $inputlDirectory + '\' + $FileName + "_" + "{0:D4}" -f + '1' + '%04d.tif'
#Calls Ghostscript command line executable to process each PDF file into a group of single page TIF image files
&'C:\Program Files\gs\gs9.14\bin\gswin64c.exe' -sDEVICE=tiffg4 -dBATCH -dNOPAUSE -q -r600 "-sOutputFile=$OutputFullDirectory" "$InputPath"
#Increment the counter for the loop
$DocCounter = $j + 1
#Rename the current pdf so that it isn't processed again.
$RenamePath = $PdfList[$j] -replace("pdf", "pd_")
Rename-Item $InputPath $RenamePath -Force
}
}
First : In PowerShell, when you call a function you must not use parenthesis :
convert $hostdirectoryPlusLoanPlusDocType
or as suggested in comment
convert -inputDirectory $hostdirectoryPlusLoanPlusDocType
but not :
convert([string]$hostdirectoryPlusLoanPlusDocType)
Second : Your function should be declarated first and use after :
function toto ($var)
{
Write-Host $var
}
toto "voila"
and not
toto "voila"
function toto ($var)
{
Write-Host $var
}

Non-Terminating Exception in Folder Deletion Script

I've written a Powershell script that would periodically delete folders on my machine.
The algorithm is as follows:
Drill down into each directory structure to the lowest subfolders
Check the creation date of the subfolder
If it's 14 days old, or older, delete it
LOG EVERYTHING (not part of the algorithm, just good practise)
When running, it operates exactly as expected...
... Except it throws the following, non-terminating exception:
Get-ChildItem : Could not find a part of the path 'C:\foo\baz'.
At C:\src\CoreDev\Trunk\Tools\BuildClean script\buildclean.ps1:55 char:15
+ Get-ChildItem <<<< -recurse -force |
+ CategoryInfo : ReadError: (C:\foo\baz:String) [Get-ChildItem],
DirectoryNotFoundException
+ FullyQualifiedErrorId : DirIOError,Microsoft.PowerShell.Commands.GetChil
dItemCommand
Why is this happening? More importantly, how can I remove it, and will it cause an issue?
The script is as follows:
# folderclean.ps1
# This script will remove each leaf node of a directory, provided that leaf is over
# 14 days old.
# CONSTANT DECLARATIONS
# testing (run on my local machine)
$proj_loc = "C:\foo", "C:\bar"
$logpath = "C:\Logs\BuildClean\$(Get-Date -format yyyyMMdd).log"
function Write-ToLogFile {
param ([string]$stringToWrite)
Add-Content $logpath -value $stringToWrite
}
# Function to check if a folder is a leaf folder.
# First, retrieve the directory $item is pointing to
# Then, create a list of children of $item that are folders
# If this list is either empty or null, return $true
# Otherwise, return $false
function Folder-IsLeaf($item) {
$ary = Get-ChildItem $item -force | ?{ $_.PSIsContainer }
if (($ary.length) -eq 0 -or $ary -eq $null) {
return $true
}
return $false
}
# Deletes leaf folders that are older than a certain threshhold.
# Get a list of children of the folder, where each child is a folder itself and
# was created over 14 days ago and the folder is a leaf
# For each of these children, delete them and increment $folderCount
# Get a list of children of the folder, where each child is a folder itself and
# was last modified over 14 days ago and the folder is a leaf
# For each of these children, delete them and increment $folderCount
function Remove-LeafFolders($path) {
$createdCount = 0
$modifiedCount = 0
Write-ToLogFile "Operation started at $(Get-Date -format "dd/MM/yyyy hh:mm:ss.fff")"
Write-ToLogFile "Looking in $proj_loc"
Write-ToLogFile ""
$start = $(Get-Date)
$proj_loc |
Get-ChildItem -recurse -force |
?{
$_.PSIsContainer -and ($_.CreationTime).AddDays(15) -lt $(Get-Date) -and $(Folder-IsLeaf $_.FullName) -eq $true
} | %{
$formattedDate = $($_.CreationTime).ToString("dd/MM/yyyy hh:mm:ss");
Write-ToLogFile "Folder $($_.FullName) is being removed; created: $formattedDate"
Remove-Item $_.FullName -recurse;
$createdCount += 1
}
$end = $(Get-Date)
$elapsed = $end - $start
Write-ToLogFile "Operation completed at $(Get-Date -format "dd/MM/yyyy hh:mm:ss.fff")."
Write-ToLogFile "Folders removed: $createdCount"
Write-ToLogFile "Time elapsed: $(($elapsed).TotalMilliseconds) ms"
Write-ToLogFile "-------------------------------"
}
Remove-LeafFolders($proj_loc)
I found this other StackOverflow question, and, after looking through the answer, I realised that the problem was the pipeline. So, I changed my code as follows:
...
$leafList = $proj_loc |
Get-ChildItem -recurse -force |
?{
$_.PSIsContainer -and ($_.CreationTime).AddDays(15) -lt $(Get-Date) -and $(Folder-IsLeaf $_.FullName) -eq $true
}
Foreach ($folder in $leafList)
{
$formattedDate = $($folder.CreationTime).ToString("dd/MM/yyyy hh:mm:ss");
Write-ToLogFile "Folder $($folder.FullName) is being removed; created: $formattedDate"
Remove-Item $folder.FullName -recurse;
$createdCount += 1
}
...
I created a few local folders and screwed around with them. No exceptions cropped up, so this appears to have worked:
Operation started at 10/12/2012 05:16:18.631
Looking in C:\foo C:\bar
Folder C:\foo\baz is being removed; created: 09/01/2010 02:00:00
Folder C:\bar\baz3\recursion is being removed; created: 01/01/2008 01:00:00
Operation completed at 10/12/2012 05:16:18.748.
Folders removed: 2
Time elapsed: 33.0033 ms
-------------------------------
Operation started at 10/12/2012 05:41:59.246
Looking in C:\foo C:\bar
Folder C:\foo\baz2\NewFolder is being removed; created: 10/10/2010 10:10:10
Folder C:\bar\baz3\barbar is being removed; created: 20/11/2012 05:37:38
Operation completed at 10/12/2012 05:41:59.279.
Folders removed: 2
Time elapsed: 21.0021 ms
-------------------------------
I think it would be easier to accomplish this sort of recursive deletion using a bottom-up approach rather than a top-down which is what Get-ChildItem gives you by default. Try this instead:
$proj_loc |
Get-ChildItem -recurse -force -Name | sort -desc | Get-Item |
? { ....
}