Adding a Write Progress bar to a script Powershell - powershell

I'm actually writing a powershell script that sort pictures and videos by dates. The script works fine but i would like to add a progress bar, and i don't know where to start, this might be a bit tricky for me, that's why i'm looking for help.
Here is the function that sort pictures
foreach ($file in $Images)
{
$Directory = $destinationDirectory + "Pictures\" + $file.LastWriteTime.Date.ToString('yyyy') + "\" + $file.LastWriteTime.Date.ToString('MMM')
if (!(Test-Path $Directory))
{
New-Item $directory -type directory
}
Copy-Item $file.fullname $Directory
}
I read the documentation about the Write-progress function but i really don't know how i'm supposed to manage it in this script

Use a variable to hold the count of files copied, increment it for each operation, then display the percentage. The example for Write-Progress has a pretty good example.
I'd recommend using the PowerShell pipeline as well instead of a foreach.
Something like this (remove the -WhatIfs when you're ready):
$images |
ForEach-Object
-Begin {
$filesCopied = 0
} `
-Process {
$Directory = "$destinationDirectory\Pictures\$($_.LastWriteTime.Date.ToString('yyyy'))\$($_.LastWriteTime.Date.ToString('MMM'))"
if (!(Test-Path $Directory)) {
New-Item $directory -type directory -WhatIf
}
Copy-Item $_.fullname $Directory -WhatIf
$filesCopied += 1
Write-Progress -Activity "Copied $_.FullName" `
-CurrentOperation "Copying $_" `
-Status "Progress:" `
-PercentComplete (($filesCopied / $Images.Count) * 100)
}

The Write-Progress cmdlet needs a bit of math to make it work for your situation. This should do it:
$i = 1
foreach ($file in $Images)
{
$Directory = $destinationDirectory + "Pictures\" + $file.LastWriteTime.Date.ToString('yyyy') + "\" + $file.LastWriteTime.Date.ToString('MMM')
if (!(Test-Path $Directory))
{
New-Item $directory -type directory
}
Copy-Item $file.fullname $Directory
[int]$Percent = $i / $Images.count * 100
Write-Progress -Activity "Copying photos" -Status "$Percent% Complete:" -PercentComplete $Percent
$i++
}
First, start with a counter variable $i and set it to 1 to represent the first item. This is setup outside the loop so that it doesn't reset back to 1 every time the loop runs.
Then, inside the loop the $Percent variable is defined by dividing the value of $i by the number of items and multiplying it by 100. Then, Write-Progress is called with the necessary parameters to display the progress. Then the counter is incremented upward by one using $i++.
Side note: $Percent is set to be an integer by placing [int] in front of it, which forces it to display whole numbers. If you want to see fractional values, just remove the [int].

Related

Write-Progress function to tell user percent completion of deletion script

I am writing a PowerShell script to delete various files and want to write a function that will prompt the user the percentage completed for each file deletion so that the user doesn't think that the script has crashed. Instead of a progress bar such as {000000.... I would rather have the screen print out a percent number like 10% complete.
#Variables
$Comp_Name = $env:COMPUTERNAME
$DateTime = Get-Date -Format "DMM-dd-yyyy_THH-mm-ss"
#Log Errors
Start-Transcript -Path C:\TEMP\error_Log-$Comp_name-$DateTime.log -Append
# Get the date and time and remove the directory specified in the function
Function Remove-Directory
{
Param([string]$Location)
$files = Get-ChildItem -Path $Location -Recurse | Where-Object {!$_.PSIsContainer}
for ($i = 1; $i -lt $Files.count; $i++) {
Write-Progress -Activity 'Deleting files...' -Status $Files[$i].FullName -PercentComplete ($i / ($Files.Count*100))
Try
{
Get-ChildItem $Location -Recurse -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue -Verbose 4>&1 | Add-Content C:\TEMP\ModifiedData-$Comp_Name-$DateTime.log
#Write-Output "SUCCESS: $Location has been been Modified Successfully"
}
Catch{Write-Output "ERROR: $Location could be Modified. Please Verify This Location Manually."}
}
}
#Remove the below files from computer
If ($Comp_Name -eq "X")
{
Remove-Directory "C:\Users\X"
}
Stop-Transcript
Write-Output "Script Process Complete. Please Check logs for additional details"
Instead of using -percentcomplete you can use one of the other status indicators
-CurrentOperation "($i / ($Files.Count*100))% complete"
You may wish to round or cast the percentage to an integer, something like:
-CurrentOperation "$([int]($i / $Files.Count*100))% complete"
The order the parameters show up is as follows, so use which one depending on how you want the hierarchy of messages to display.
-Activity
-Status
-CurrentOperation

Progress Bar Login in Powershell Delete Task

Good afternoon Stack Community,
I'm new to progress bar displays in PS. But after reviewing the documentation and a few forums I've ended up with the script below. It just doesn't do anything. It does not delete the folder/subfolders, nor does it display a progress bar, nor does it error out. Is my logic out of context or is there a better way to nest the progress bar with the associated task?
$computerlist = get-content -path c:\scripts\Computerlist-test.txt
$FolderPath = "c$\IT\9.0.0"
$Path = "\\$computer\$FolderPath"
foreach ($computer in $computerlist) {
for ($i = 1; $i -lt $ListOfFiles.count; $i++) {
Write-Progress -Activity 'Deleting Folders/Files...' -Status $ListOfFiles[$i].FullName -PercentComplete ($i / ($ListOfFiles.Count*100))
Remove-Item -path $Path -Recurse -WhatIf
$ListOfFiles = Get-ChildItem -Path $Path -Recurse | Where-Object {!$_.PSIsContainer}
}
}
The remove-item portion works without the secondary "I" loop for the taskbar so I am assuming this is illogical.

If a job has status completed execude code in powershell

After about half a day of googling without any luck I've decided to post my question here.
So I want to check if a job has status completed.
Here is my code so far:
Start-job -name Copy -Scriptblock {Copy-Item -force -recurse -path
"C:\Test.temp" -destination "D:\"}
$x = 170 $length = $x / 100 While($x -gt 0) {
$min ) [int](([string]($x/60)).Split('.')[0]) $Text = " " + $min + "
mintues " + ($x % 60) + " seconds left" Write-progress "Copying file,
estimated time remaning" -status $text -percentComplete ($x/$length)
Start-sleep -s 1 $x--
if (Get-job -name Copy -status "Completed") { Break } }
So as you can see I first start a job, then I run a loop with a countdown progress bar. This is just to give the user some kind of feedback that things are still moving. Note that the "Get-job -status "completed" doesn't work since it's not a parameter of Get-job.
The problem now is that I can't get the "if get job has completed, break the loop" since the copying job might be done before the progress bar.
Does anyone know a good solution to this?
Thanks in advance!
Using ForEach is probably your best bet for breaking the loop cleanly once done.
This shows a progress bar but no timer, more so just a per file progress so it will tell you what it is up to and whats taking a while.
$srcPath = 'C:\Test.temp'
$destPath = 'D:\'
$files = Get-ChildItem -Path $srcPath -Recurse
$count = $files.count
$i=0
ForEach ($file in $files){
$i++
Write-Progress -activity "Copying from $srcPath to $destPath" -status "$file ($i of $count)" -percentcomplete (($i/$count)*100)
if($file.psiscontainer){
$sourcefilecontainer = $file.parent
} else {
$sourcefilecontainer = $file.directory
}
$relativepath = $sourcefilecontainer.fullname.SubString($srcPath.length)
Copy-Item $file.fullname ($destPath + $relativepath) -force
}
I think you could get the condition to work like this:
if ( Get-job -State Completed | Where-Object {$_.Name.Contains("Copy"){ Break }
Your -status does not exist in the get-job

Powershell Script to delete files not in a list with progress bar

I have a folder with .jpg files in it. I associate these with products in an access database. One of the sources for products provides these .jpg files but they do not allow you to easily download only the pictures that you currently use. Therefore I have found a PowerShell Script to delete the files that I do not need.
$exclusions = Get-Content C:\Users\office\Desktop\ExcludedPhotos.txt
dir -rec M:\PhotoDirectory\PhotoFolder | Where-Object {$exclusions -notcontains $_.name } | Remove-Item
Credit to #x0n Powershell script to delete files not specified in a list
And it works great! but the problem is it takes forever and I have over 180,000 items to search through and delete. So I wanted to make a progress bar that would let me know how far I had gone through the process.
So after a little bit of searching I found an article called "using the progress bar"
The problem is I don't know how to mash the two together, However I have made an attempt here:
$exclusions = Get-Content C:\Users\office\Desktop\ExcludedPhotos.txt
1..100 | foreach-object {
Write-Progress -Activity "Deleting Files" -Status "$_ %" -Id 1 -PercentComplete $_ -CurrentOperation "Deleting File $_"
dir -rec M:\PhotoDirectory\PhotoFolder | Where-Object {$exclusions -notcontains $_.name } | Remove-Item
}
However that seems to take even longer than the original script did, I don't know exactly how it is working, and I am testing it when I only need to remove 10-15 files.
It is likely there is something really basic I am missing But I would really appreciate some help understanding this.
Here I have added a screenshot:
PowerShell Console
However that seems to take even longer than the original script did
That's because you're attempting to enumerate, filter and delete the files 100 times - which is obviously unnecessary.
What you want to do is calculate how far in the process of deleting the files you are, and then use that as a basis for the percentage you pass to Write-Progress. The easiest way to keep count is probably to use a regular for loop:
Note: these first two examples are very generic, go to the bottom for an example that takes your volume (~180.000 files) into account.
# Grab all the files you need to delete
$exclusions = Get-Content C:\Users\office\Desktop\ExcludedPhotos.txt
$filesToDelete = Get-ChildItem M:\PhotoDirectory\PhotoFolder -Recurse | Where-Object {$exclusions -notcontains $_.Name }
for($i = 0; $i -lt $filesToDelete.Count; $i++){
# calculate progress percentage
$percentage = ($i + 1) / $filesToDelete.Count * 100
Write-Progress -Activity "Deleting Files" -Status "Deleting File #$($i+1)/$($filesToDelete.Count)" -PercentComplete $percentage
# delete file
$filesToDelete[$i] |Remove-Item
}
# All done
Write-Progress -Activity "Deleting Files" -Completed
Another characteristic you might want to use to indicate a percentage is the relative volume (total number of bytes) removed. We can do this by simply keeping track of how many bytes we need to remove total, and how many we've removed so far:
$exclusions = Get-Content C:\Users\office\Desktop\ExcludedPhotos.txt
$filesToDelete = Get-ChildItem M:\PhotoDirectory\PhotoFolder -Recurse | Where-Object {$exclusions -notcontains $_.Name }
$TotalSize = ($filesToDelete |Measure-Object -Property Length -Sum).Sum
$BytesRemoved = 0
foreach($file in $filesToDelete){
$percentage = $BytesRemoved / $TotalSize * 100
Write-Progress -Activity "Deleting Files" -Status "Deleted $BytesRemoved/$TotalSize bytes" -PercentComplete $percentage
$file |Remove-Item
$BytesRemoved += $file.Length
}
Write-Progress -Activity "Deleting Files" -Completed
As #MatthewWetmore points out, invoking Write-Progress every time you delete a file will of course incur some overhead. And with 180.000 files, you're probably not that interested in having the UI update when the progress goes from 3.56325% to 3.56331%
What you could do is use the for loop to count in increments of 1% of the entire set of items, and then remove a whole range of files on each iteration:
[int]$hundredthStep = $filesToDelete.Count / 100
for($i = 0; $i -lt $filesToDelete.Count; $i += $hundredthStep){
# calculate progress percentage
$percentage = ($i + 1) / $filesToDelete.Count * 100
Write-Progress -Activity "Deleting Files" -Status "Deleting File up to #$($i+1)/$($filesToDelete.Count)" -PercentComplete $percentage
# delete file
$filesToDelete[$i..($i + $hundredthStep - 1)] |Remove-Item
}
# All done
Write-Progress -Activity "Deleting Files" -Completed
You want to do something like this (beware this is untested so i've put -whatif on the remove cmdlet which you should remove when you're happy it's working correctly):
$exclusions = Get-Content C:\Users\office\Desktop\ExcludedPhotos.txt
$files = dir -rec M:\PhotoDirectory\PhotoFolder | Where-Object {$exclusions -notcontains $_.name }
$files | foreach-object {
$num += 1
Write-Progress -Activity "Deleting Files" -Status "$_ %" -Id 1 -PercentComplete (($num / $files.count) * 100) -CurrentOperation "Deleting File $($_.name)"
$_ | Remove-Item -WhatIf
}

Powershell copy file after a date has passed with file structure

I am trying to copy a file off a server and onto another, I want to keep the structure of the file like so C:\folder\folder\file! If the folder is there copy the file into it, if it is not then create the folders and then copy into it!
I would like it also to filter out the files that are still needed so I want to keep files for 30 days and then move them!
Blockquote
`[int]$Count = 0
$filter = (Get-Date).AddDays(-15).ToString("MM/dd/yyyy")
Get-WMIObject Win32_LogicalDisk | ForEach-Object{
$SearchFolders = Get-Childitem ($_.DeviceID + "\crams") -recurse
$FileList = $SearchFolders |
Where-Object {$_.name -like "Stdout_*" -and $_.lastwritetime -le $filter}
[int]$Totalfiles = ($FileList | Measure-object).count
write-host "There are a total of $Totalfiles found."
echo $FileList
start-sleep 30
[int]
ForEach ($Item in $FileList)
{$Count++
$File = $Item
Write-Host "Now Moving $File"
$destination ="C:\StdLogFiles\"
$path = test-Path (get-childitem $destination -Exclude "Stdout_*")
if ($path -eq $true) {
write-Host "Directory Already exists"
copy-item $File -destination $destination
}
elseif ($path -eq $false) {
cd $destination
mkdir $File
copy-Item $File -destination $destination
}
}
}`
Is what I have so far it has changed a lot due to trying to get it to work but the search works and so does the date part I can not get it to keep the structure of the file!
Okay I took out the bottom part and put in
ForEach ($Item in Get-ChildItem $FileList)
also tried get-content but path is null
{$Count++
$destination = "C:\StdLogFiles"
$File = $Item
Write-Host "Now Moving $File to $destination"
Copy-Item -Path $file.fullname -Destination $destination -force}}
it is copying everything that is in c into that folder but not the files I do not understand what it is doing now! I had it copying the files even wen back to an older version and can't get it to work again! I am going to leave it before I break it more!
Any help or thoughts would be appreciated
I think RoboCopy is probably a simpler solution for you to be honest. But, if you insist on using PowerShell you are going to need to setup your destination better if you want to keep your file structure. You also want to leave your filter date as a [DateTime] object instead of converting it to a string since what you are comparing it to (lastwritetime) is a [DateTime] object. You'll need to do something like:
$filter = (Get-Date).AddDays(-15)
$FileList = Get-WMIObject Win32_LogicalDisk | ForEach-Object{
Get-Childitem ($_.DeviceID + "\crams") -recurse | Where-Object {$_.name -like "Stdout_*" -and $_.lastwritetime -le $filter}
}
$Totalfiles = $FileList.count
For($i = 1;$i -le $TotalFiles; $i++)
{
$File = $FileList[($i-1)]
Write-Progress -Activity "Backing up old files" -CurrentOperation ("Copying file: " + $file.Name) -Status "$i of $Totalfiles files" -PercentComplete ($i*100/$Totalfiles)
$Destination = (Split-Path $file.fullname) -replace "^.*?\\crams", "C:\StdLogFiles"
If(!(Test-Path $Destination)){
New-Item -Path $Destination -ItemType Directory | Out-Null
}
Copy-Item $File -Destination $Destination
}
Write-Progress -Completed
That gathers all the files you need to move from all disks. Takes a count of them, and then enters a loop that will cycle as many times as you have files. In the loop is assigns the current item to a variable, then updates a progress bar based on progress. It then parses the destination by replacing the beginning of the file's full path (minus file name) with your target destination of 'C:\StdLogFiles'. So D:\Crams\HolyPregnantNunsBatman\Stdout04122015.log becomes C:\StdLogFiles\HolyPregnantNunsBatman. Then it tests the path, and if it's not valid it creates it (piped to out-null to avoid spam). Then we copy the file to the destination and move on to the next item. After the files are done we close out the progress bar.