I've encountered a problem with my script, whenever I do a trashbin deletion and the map location has a folder within a folder that has an file that has to be deleted, it will not work.
The problem is that I need to add a recurse and -erroraction silently continue. But I dont know how I can do that since the methods for moving files to trash bin are quite hard in my opinion.
Could you guys help me out?
My code:
## Top of the script
param(
[Parameter(Mandatory=$true)]
[ValidateRange(0,99999)]
[int]$minutes,
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]$maplocation,
[Parameter(Mandatory=$true)]
[ValidateSet("Direct","TrashBin")]
[string]$consequence
)
## Variables
$maxAge = (Get-Date).AddMinutes(-$minutes)
$files = Get-ChildItem $maplocation -Recurse
$time = get-date
$fortrashbin = $maplocation + '\' + $file
##
foreach ($file in $files)
{
if ($file.lastwritetime -lt $maxage)
{
switch ($consequence)
{
"direct"
{
write-verbose "File Found $file" -verbose
remove-item $file.fullname -recurse -erroraction silentlycontinue
write-verbose "Deleting $file" -verbose
}
"trashbin" {
write-verbose "File Found $file" -verbose
write-verbose "moving $file to trashbin" -verbose
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fortrashbin,'OnlyErrorDialogs','SendToRecycleBin')
}
}
}
}
I think you don't have to iterate over each file if you delete a folder. Instead, you can use the DeleteDirectory method:
if (Test-Path -Path $maplocation -PathType Container)
{
# delete directory
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory(
$maplocation,'OnlyErrorDialogs','SendToRecycleBin')
}
else
{
# delete folder
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile(
$maplocation,'OnlyErrorDialogs','SendToRecycleBin')
}
Please also consider to replace your $consequence parameter with a [switch] like:
param
(
[Parameter(Mandatory=$true)]
[ValidateRange(0,99999)]
[int]$minutes,
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]$maplocation,
[Parameter(Mandatory=$false)]
[switch]$Direct
)
Now you can invoke your script without any parameters to delete the location to trashbin or with the -Direct switch to permanently remove it.
Related
I'm writing a script where I'm trying to output the results from a Get-ChildItem command to a log file. The script below is simplified to show the issue I'm having. For example, the WriteLog function is used several times in the actual Script. The file listing is not the only thing to be added to the log file.
The snippet below writes a long run-on line of all full filenames to the log.
$FilePath = "G:\Folder"
$LogPathName = "G:\Folder\TestLog.log"
Function WriteLog {
Param ([string]$LogString)
$Stamp = Get-Date
$LogMessage = "$Stamp - $LogString"
Add-Content $LogPathName -value $LogMessage
}
$FileList = Get-ChildItem –Path $FilePath -include ('*.csv', '*.xlsx')
writelog $FileList
I want each filename to begin on a new line--like a list. How can I do this?
Leaving your function WriteLog as is, the workaround is to iterate over each element of the array returned by Get-ChildItem so that the function appends to the file line-by-line:
foreach($item in $FileList) {
WriteLog $item
}
However a more elegant way of approaching this would be to leverage ValueFromPipeline, then you could simply pipe Get-ChildItem into your function and let the process block handle each element. You can also add a -PassThru switch to it in case you also want the same object be returned as output for later manipulation. And lastly, it may be worth adding a new -Path parameter to make it reusable.
function Write-Log {
param(
[Parameter(ValueFromPipeline, Mandatory)]
[object] $InputObject,
[parameter(Mandatory)]
[string] $Path,
[parameter()]
[switch] $PassThru
)
begin { $sb = [System.Text.StringBuilder]::new() }
process {
$sb = $sb.AppendLine("$(Get-Date) - $InputObject")
if($PassThru.IsPresent) { $InputObject }
}
end { Add-Content $Path -Value $sb.ToString() -NoNewline }
}
$FileList = Get-ChildItem -Path .\* -Include '*.csv', '*.xlsx' |
Write-Log -Path "G:\Folder\TestLog.log" -PassThru
I´m facing to an issue that i can´t figure it out, how can i solve.
Basically i´ve an function that copy files from one directory to another directory, and rename it, if they already exists.
In the mean while, if any file with case sensitive name, it don´t copy it at all.
example:
TEXT.xml
text.xml
It just copy one of them. i need to copy both files.
Now, is the Rename-Item or the Copy-Item, that can´t deal with this case sensitive? Any idea how can i solve this?
Thanks for any help
My code:
Function Copy-File {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$Origin,
[Parameter(Mandatory = $true, Position = 1)]
[string]$Destination
)
# check if a file with that name already exists within the destination
$fileName = Join-Path $Destination ([System.IO.Path]::GetFileName($Origin))
if (Test-Path $fileName -PathType Leaf){
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($Origin)
$extension = [System.IO.Path]::GetExtension($Origin) # this includes the dot
$allFiles = Get-ChildItem $Destination | Where-Object {$_.PSIsContainer -eq $false} | Foreach-Object {$_.Name}
$newName = $baseName + $extension
$count = 1
while ($allFiles -contains $newName) {
$newName = [string]::Format("{0}({1}){2}", $baseName, $count.ToString(), $extension)
$count++
}
# rename the file already in the destination folder
# Write-Verbose -Message "Renaming file '$fileName' to '$newName'"
Rename-Item $fileName -NewName $newName
}
#Write-Verbose -Message "Moving file '$Origin' to folder '$Destination'"
Copy-Item $Origin $Destination
}
All of what you are after here is just doing this...
(always use validation testing for all destructive commands/code --- (Delete/Rename/Modify/Update/Move))
Clear-Host
$SourceFiles = 'D:\Temp\Source'
$Destination = 'D:\Temp\Destination'
$Counter = 0
Get-ChildItem -Path $SourceFiles |
ForEach{
Try
{
If(Test-Path -Path $Destination\$PSItem)
{
$Counter++
Write-Warning -Message "$($PSItem.Name) already exits. Renaming destination file."
Rename-Item -Path $Destination\$PSItem -NewName "$($PSItem.Basename)_$Counter$($PSitem.Extension)" -WhatIf
# Copy-Item -Path $PSItem.FullName -Destination $Destination -WhatIf
}
Else
{
Write-Verbose -Message "$($PSItem.Name) does not exist. Copying file." -Verbose
Copy-Item -Path $PSItem.FullName -Destination $Destination -WhatIf
}
}
Catch {$PSItem.Exception.Message}
}
# Results
<#
VERBOSE: 5 Free Software You'll Wish You Knew Earlier! 2019 - YouTube.url does not exist. Copying file.
What if: Performing the operation "Copy File" on target "Item: D:\Temp\Source\5 Free Software You'll Wish You Knew Earlier! 2019 - YouTube.url Destination: D:\Temp\Destination\5 Free Software You'll Wish You Knew Earlier! 2019 - YouTube.url".
WARNING: audiolengthCLEAN.csv already exits. Renaming destination file.
What if: Performing the operation "Rename File" on target "Item: D:\Temp\Destination\audiolengthCLEAN.csv Destination: D:\Temp\Destination\audiolengthCLEAN_2.csv".
...
#>
Update as per your function question.
Function Sync-FileArchive
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('sfa')]
Param
(
[Parameter(Mandatory = $true, Position = 0)]
[string]$SourceFiles,
[Parameter(Mandatory = $true, Position = 1)]
[string]$Destination
)
$Counter = 0
Get-ChildItem -Path $SourceFiles |
ForEach{
Try
{
If(Test-Path -Path $Destination\$PSItem)
{
$Counter++
Write-Warning -Message "$($PSItem.Name) already exits. Renaming destination file."
Rename-Item -Path $Destination\$PSItem -NewName "$($PSItem.Basename)_$Counter$($PSitem.Extension)" -WhatIf
}
Else
{
Write-Verbose -Message "$($PSItem.Name) does not exist. Copying file." -Verbose
Copy-Item -Path $PSItem.FullName -Destination $Destination
}
}
Catch {$PSItem.Exception.Message}
}
}
# Example1 - Remove the -WhatIf to fully execute.
Sync-FileArchive -SourceFiles 'D:\Temp\Source' -Destination 'D:\Temp\Destination' -WhatIf
# Example2
sfa 'D:\Temp\Source' 'D:\Temp\Destination' -WhatIf
Quick question regarding the PowerShell Copy-Item command. I was wondering if you have a directory structure and wanted to overwrite another directory structure is there a way to run the Copy-Item command in a 'preview' mode. It would output what files its overwriting from directory a to directory b but not actually perform the Copy-Item command.
Any help or advice appreciated.
Thanks.
Interesting question!
Here is my attempt of doing it all in Powershell, so not needing RoboCopy.
function Copy-Preview {
[CmdletBinding(DefaultParameterSetName = 'ByPath')]
param(
[Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'ByPath', Position = 0)]
[ValidateScript({ Test-Path $_ })]
[string]$Path,
[Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'ByLiteralPath', Position = 0)]
[ValidateScript({ Test-Path $_ })]
[string]$LiteralPath,
[Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 1)]
[string]$Destination,
[string]$Filter = $null,
[string]$Include = $null,
[string]$Exclude = $null,
[switch]$Recurse,
[switch]$Force
)
if ($PSCmdlet.ParameterSetName -eq 'ByLiteralPath') { $Path = $LiteralPath }
# determine if $Path and $Destination hold a file or a directory
$srcIsFolder = (Test-Path $Path -PathType Container -ErrorAction SilentlyContinue)
# cannot use Test-Path here because then the path has to exist.
# assume if it has an extension the path is a file, otherwise a directory
# NOTE:
# This is certainly not fullproof, so to avoid problems, always make sure
# the destination ends with a backslash if a directory is intended.
$destIsFolder = (-not ([System.IO.Path]::HasExtension($Destination)))
if ($destIsFolder -and !(Test-Path $Destination -PathType Container)) {
Write-Host "Destination path does not exist yet. All files from '$Path' will be copied fresh" -ForegroundColor Green
return
}
elseif ($srcIsFolder -and (!$destIsFolder)) {
# should not happen: source is a directory, while the destination is a file..
Write-Error "When parameter Path points to a directory, the Destination cannot be a file.."
return
}
$count = 0
if ($srcIsFolder -and $destIsFolder) {
# Both the source and the destinations are folders
# make sure both paths are qualified for .Replace() further down
if (-not $Path.EndsWith("\")) { $Path += "\" }
if (-not $Destination.EndsWith("\")) { $Destination += "\" }
$splat = #{
Filter = $Filter
Include = $Include
Exclude = $Exclude
Recurse = $Recurse
Force = $Force
}
# add either Path or LiteralPath to the parameters as they are mutually exclusive
if ($PSCmdlet.ParameterSetName -eq 'ByPath') { $splat.Path = $Path }
else { $splat.LiteralPath = $LiteralPath }
$srcFiles = Get-ChildItem #splat | Select-Object -ExpandProperty FullName
# reuse the splat parameters hash for the destination, but change the Path
if ($splat.LiteralPath) {($splat.Remove("LiteralPath"))}
$splat.Path = $Destination
$destFiles = Get-ChildItem #splat | Select-Object -ExpandProperty FullName
foreach ($srcItem in $srcFiles) {
$destItem = $srcItem.Replace($Path, $Destination)
if ($destFiles -contains $destItem) {
Write-Host "'$destItem' would be overwritten"
$count++
}
}
}
elseif (!$srcIsFolder) {
# the source is a file
if (!$destIsFolder) {
# the destination is also a file
if (Test-Path $Destination -PathType Leaf) {
Write-Host "'$Destination' would be overwritten"
$count++
}
}
else {
# source is file, destination is a directory
$destItem = Join-Path $Destination (Split-Path $Path -Leaf)
if (Test-Path $destItem -PathType Leaf) {
Write-Host "'$destItem' would be overwritten"
$count++
}
}
}
$msg = "$count item{0} would be overwritten by Copy-Item" -f $(if ($count -ne 1) { 's' })
$dash = "-" * ($msg.Length)
Write-Host "$dash`r`n$msg" -ForegroundColor Green
}
tl;dr:
Copy-Item -WhatIf will not give you the level of detail you're looking for - see below.
Use robocopy.exe -l instead (Windows only), as Ansgar Wiechers recommends, because it individually lists what files would be copied, including dynamically omitting those already present in the target dir (with the same size and last-modified time stamp, by default).
Generally, robocopy is faster and more fully featured than Copy-Item, and it avoids a notable pitfall of the latter.
Get-Help about_CommonParameters documents the -WhatIf common parameter supported by many (but not all) cmdlets, whose purpose is to preview an operation without actually performing it.
However, this feature is implemented in an abstract fashion, and often doesn't provide information as detailed as one would hope.
Case in point: while Copy-Item does support -WhatIf, it probably won't give you the level of detail you're looking for, because if the source item is a directory, only a single line such as the following is output:
What if: Performing the operation "Copy Directory" on target "Item: sourceDir Destination: destDir".
Note that you'll see the same line whether or not your Copy-Item call includes the -Recurse switch.
Even if you ensure existence of the target directory manually and append /* to the source directory path in order to see individual filenames, you'd only see them at the child level, not further down the subtree, and you'd never get the dynamic information that robocopy -l provides with respect to what files actually need replacement.
You can use the -whatif parameter.
Copy-item -path myfile.txt -destination myfoldertoCopyTo\ -whatif
I'm facing an issue with write-zip : The second time I call Write-Zip with -Append parameter I get an error : Extra data extended Zip64 information length is invalid.
I'm using PSCX 2.1.x.
I got around the issue by creating a function that, rather than appending a new file, expends the existing zip if any to a temp folder, adds the new file to the folder, and then zip all files in one time.
That works... but it is not very efficient.
Is there any solution ?
Here is my working function :
Function Add-To-Zip {
Param (
[Parameter(Mandatory=$True)]
[String] $Path,
[Parameter(Mandatory=$True)]
[String] $OutputPath,
[Parameter(Mandatory=$True)]
[String] $TempPath,
[Parameter(Mandatory=$False)]
[Boolean] $RemoveOriginal = $False
)
If (Test-Path $TempPath) {
Remove-Item -Path $TempPath -Recurse
}
New-Item -ItemType Directory -Path $TempPath
$paths = #()
Copy-Item $Path $TempPath
If (Test-Path $OutputPath) {
Expand-Archive -Path $OutputPath -OutputPath $TempPath
}
Get-ChildItem $TempPath | ForEach { $paths += Get-Item $_.FullName }
Try {
$paths | Write-Zip -OutputPath $OutputPath -Quiet -FlattenPaths -EntryPathRoot $TempPath
If ($RemoveOriginal) {
Remove-Item -Path $Path
}
} Catch {
Throw "An error occured while adding '$FilePath' to '$ZipPath'."
}
If (Test-Path $TempPath) {
Remove-Item -Path $TempPath -Recurse
}
}
When using the rm command to delete files in Powershell, they are permanently deleted.
Instead of this, I would like to have the deleted item go to the recycle bin, like what happens when files are deleted through the UI.
How can you do this in PowerShell?
2017 answer: use the Recycle module
Install-Module -Name Recycle
Then run:
Remove-ItemSafely file
I like to make an alias called trash for this.
If you don't want to always see the confirmation prompt, use the following:
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('d:\foo.txt','OnlyErrorDialogs','SendToRecycleBin')
(solution courtesy of Shay Levy)
It works in PowerShell pretty much the same way as Chris Ballance's solution in JScript:
$shell = new-object -comobject "Shell.Application"
$folder = $shell.Namespace("<path to file>")
$item = $folder.ParseName("<name of file>")
$item.InvokeVerb("delete")
Here is a shorter version that reduces a bit of work
$path = "<path to file>"
$shell = new-object -comobject "Shell.Application"
$item = $shell.Namespace(0).ParseName("$path")
$item.InvokeVerb("delete")
Here's an improved function that supports directories as well as files as input:
Add-Type -AssemblyName Microsoft.VisualBasic
function Remove-Item-ToRecycleBin($Path) {
$item = Get-Item -Path $Path -ErrorAction SilentlyContinue
if ($item -eq $null)
{
Write-Error("'{0}' not found" -f $Path)
}
else
{
$fullpath=$item.FullName
Write-Verbose ("Moving '{0}' to the Recycle Bin" -f $fullpath)
if (Test-Path -Path $fullpath -PathType Container)
{
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
else
{
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
}
}
Remove file to RecycleBin:
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('e:\test\test.txt','OnlyErrorDialogs','SendToRecycleBin')
Remove folder to RecycleBin:
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::Deletedirectory('e:\test\testfolder','OnlyErrorDialogs','SendToRecycleBin')
Here's slight mod to sba923s' great answer.
I've changed a few things like the parameter passing and added a -WhatIf to test the deletion for the file or directory.
function Remove-ItemToRecycleBin {
Param
(
[Parameter(Mandatory = $true, HelpMessage = 'Directory path of file path for deletion.')]
[String]$LiteralPath,
[Parameter(Mandatory = $false, HelpMessage = 'Switch for allowing the user to test the deletion first.')]
[Switch]$WhatIf
)
Add-Type -AssemblyName Microsoft.VisualBasic
$item = Get-Item -LiteralPath $LiteralPath -ErrorAction SilentlyContinue
if ($item -eq $null) {
Write-Error("'{0}' not found" -f $LiteralPath)
}
else {
$fullpath = $item.FullName
if (Test-Path -LiteralPath $fullpath -PathType Container) {
if (!$WhatIf) {
Write-Verbose ("Moving '{0}' folder to the Recycle Bin" -f $fullpath)
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
else {
Write-Host "Testing deletion of folder: $fullpath"
}
}
else {
if (!$WhatIf) {
Write-Verbose ("Moving '{0}' file to the Recycle Bin" -f $fullpath)
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
else {
Write-Host "Testing deletion of file: $fullpath"
}
}
}
}
$tempFile = [Environment]::GetFolderPath("Desktop") + "\deletion test.txt"
"stuff" | Out-File -FilePath $tempFile
$fileToDelete = $tempFile
Start-Sleep -Seconds 2 # Just here for you to see the file getting created before deletion.
# Tests the deletion of the folder or directory.
Remove-ItemToRecycleBin -WhatIf -LiteralPath $fileToDelete
# PS> Testing deletion of file: C:\Users\username\Desktop\deletion test.txt
# Actually deletes the file or directory.
# Remove-ItemToRecycleBin -LiteralPath $fileToDelete
Here is a complete solution that can be added to your user profile to make 'rm' send files to the Recycle Bin. In my limited testing, it handles relative paths better than the previous solutions.
Add-Type -AssemblyName Microsoft.VisualBasic
function Remove-Item-toRecycle($item) {
Get-Item -Path $item | %{ $fullpath = $_.FullName}
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($fullpath,'OnlyErrorDialogs','SendToRecycleBin')
}
Set-Alias rm Remove-Item-toRecycle -Option AllScope