Remove-Item command is not removing a folder and its contents - powershell

I have an application that will be un installed across our estate, so I am creating a PowerShell script to do this (it will be distributed through Intune). However after the uninstall a folder and 2 files remain. I added in Remove-Item but it will not remove the folder and content even with -Force and -Recurse included as it states it is in use, but I can manually delete the folder and contents without any issues.
My script is (I have changed sensitive information)
$FileName = "C:\Users\$env:USERNAME\AppData\Local\my_app\app1.exe"
if (Test-Path $FileName) {
Write-Host "File Exists"
}
else
{
Write-Host "File Doesn't Exists"
exit
}
cd "C:\Users\$env:USERNAME\AppData\Local\my_app"
.\Update.exe --uninstall
Remove-Item -Path C:\Users\$env:USERNAME\AppData\Local\my_app -Force -Recurse
The actual error states
Remove-Item : Cannot remove the item at 'C:\Users\superuser\AppData\Local\my_app' because it is in use
At line:21 char:1
+ Remove-Item -Path C:\Users\$env:USERNAME\AppData\Local\my_app - ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Remove-Item], PSInvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.RemoveItemCommand

You change locations into the same directory you are trying to delete. You need to change out of that directory. You have a few options here:
The Obvious Solution (Once You Know the Issue)
Change out of the directory:
cd .. # any path that is not part of the directory tree you want to delete
But PowerShell has some more useful ways to traverse directories. Read on for more information.
Solution 2: Windows PowerShell - Use the Location Stack
Opt for using Push-Location and Pop-Location instead of cd (aliased to Set-Location).Push/Pop-Location use the location stack. Use Push-Location to change to new directories and Pop-Location to walk backwards. In your case, this would work:
Push-Location "C:\Users\$env:USERNAME\AppData\Local\my_app"
.\Update.exe --uninstall
Pop-Location # This will return to the previous directory
Remove-Item -Path C:\Users\$env:USERNAME\AppData\Local\my_app -Force -Recurse
Solution 3: PowerShell Core - Use cd -
If you can utilize PowerShell Core, the same concept applies but PowerShell Core supports the bash-like cd - and cd + since version 6.2. Similar to using Push/Pop-Location, you can walk backwards and forwards to the directories you've been to. In your case, cd - would work like so:
cd "C:\Users\$env:USERNAME\AppData\Local\my_app"
.\Update.exe --uninstall
cd - # This will return to the previous directory
Remove-Item -Path C:\Users\$env:USERNAME\AppData\Local\my_app -Force -Recurse
From the Set-Location documentation for PowerShell Core:
PowerShell 6.2 added support for - and + as a values for the Path parameter. PowerShell maintains a history of the last 20 locations that can be accessed with - and +. This list is independent from the location stack that is accessed using the StackName parameter.

Related

Rename-Item fails with 'Cannot rename because item at 'C:\Folder\ Example App Folder ' does not exist

I have a folder that has 100s of subfolders and files within those subfolders that have names with leading and trailing spaces. These folders and files were created using a Node JS app. I need to now remove the leading and trailing spaces from the file names of the folders and files.
I found this script which seems to be built for this purpose.
If I use the function to create files/folders with leading spaces mentioned on the same blog, the script is able to rename those.
However, it does not work with files/folders created using the node JS app. When renaming the folder/file, it fails with -
Rename-Item : Cannot rename because item at 'C:\Folder\ Example App Folder ' does not exist.
At C:\renamefileswithspaces.ps1:25 char:13
+ Rename-Item -LiteralPath $_.fullname -NewName (“{0}{1}{2} ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Rename-Item], PSInvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.RenameItemCommand
If I run the following cmdlets for just one folder (outside the script), it fails with the same error -
$f = Get-ChildItem -Path 'C:\Folder' -Filter "*Example*"
Rename-Item -LiteralPath $f.FullName -NewName $f.Name.Trim()
Rename-Item : Cannot rename because item at 'C:\Folder\ Example App Folder ' does not exist.
At line:1 char:1
+ Rename-Item -LiteralPath $f.FullName -NewName $f.Name.Trim()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Rename-Item], PSInvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.RenameItemCommand
Why does Rename-Item fail for files/folders created using the app whereas it works for files/folders created using PowerShell?
Edit: Running on Windows Server 2016 Standard. PowerShell version - 5.1.14393.3866
Edit 2: Updated to PowerShell 7.1.4 from here and the issue still exists.
Trailing spaces in file / directory names are problematic on Windows (unless a filename extension is also present, and the trailing spaces are only in the base file name):
PowerShell, .NET, cmd.exe, and File Explorer:
can NOT create items with such names, because the given name invariably has trailing whitespace removed before it is used.
can NOT copy, rename, or delete such items (if they were created through other means, such as Node.js - see below), for the same reason: the given name / path has trailing spaces removed, and looking for the resulting, modified name fails.
CAN enumerate such items (Get-ChildItem, [System.IO.Directory]::EnumerateDirectories(), dir, lists of files in File Explorer).
While the file-system itself allows such names, it's clearly not a good idea to create them.
Node.js allows you to create them, but fortunately also allows you to target them later for renaming or deletion.
Thus, the workaround is to use Node.js for renaming too; e.g.:
node -e "fs.renameSync(' Example App directory ', 'Example App directory')"
Here's a self-contained example that uses a temporary folder to enumerate all subdirectories and rename them by removing leading and trailing whitespace from their names, if needed:
# Switch to a temporary directory.
Push-Location -ea Stop ($tmpDir = (New-Item -Type Directory -Force (Join-Path $env:TEMP/ $PID)).FullName)
try {
# Create a test directory whose name has trailing (and leading) spaces.
# Note:
# * You cannot do this with PowerShell / cmd.exe commands.
# * To delete or rename such a directory, use Node.js too:
# node -e "fs.rmdirSync(' Example App directory ')"
# node -e "fs.renameSync(' Example App directory ', 'Example App directory')"
node -e "fs.mkdirSync(' Example App directory ')"
if ($LASTEXITCODE) { throw }
# Create another directory, but without leading or trailing whitespace.
$null = New-Item -Type Directory 'Example without surrounding spaces'
# Enumerate all directories and rename them, if needed,
# by trimming leading and trailing whitespace.
# Note: The Get-ChildItem call is enclosed in (...) to guard against renamed directorys
# re-entering the enumeration.
(Get-ChildItem -LiteralPath . -Directory -Filter *) | ForEach-Object {
# Determine the full path with the name part trimmed.
$trimmedPath = Join-Path (Split-Path -LiteralPath $_.FullName) ($_.BaseName.Trim() + $_.Extension.Trim())
if ($trimmedPath -ne $_.FullName) { # Trimming is needed.
Write-Verbose -vb "Renaming '$($_.FullName)' to '$($trimmedPath)'..."
# Perform the renaming via Node.js
node -e ("fs.renameSync('{0}', '{1}')" -f ($_.FullName -replace "[\\']", '\$0'), ($trimmedPath -replace "[\\']", '\$0'))
if ($LASTEXITCODE) { Write-Error "Renaming '$($_.FullName)' to '$($trimmedPath)' failed with exit code $LASTEXITCODE." }
}
else { # Trimming not needed.
Write-Verbose -vb "Name has no leading or trailing spaces: '$($_.FullName)'"
}
}
Write-Verbose -vb "Names after:"
(Get-ChildItem).Name.ForEach({"[$_]"})
}
finally {
# Clean up.
Pop-Location; Remove-Item $tmpDir -Recurse
}
Sample output:
VERBOSE: Renaming 'C:\Users\jdoe\AppData\Local\Temp\3712\ Example App directory ' to 'C:\Users\jdoe\AppData\Local\Temp\3712\Example App directory'...
VERBOSE: Name has no leading or trailing spaces: 'C:\Users\jdoe\AppData\Local\Temp\3712\Example without surrounding spaces'
VERBOSE: Names after:
[Example App directory]
[Example without surrounding spaces]
Note:
The approach of calling the Node.js CLI, node, for every input directory isn't efficient, but for a one-time cleanup operation that probably won't matter.
As workaround you can use the .MoveTo() method that both DirectoryInfo and FileInfo objects have.
For me this works:
# get both files and directories
Get-ChildItem -Path 'C:\Folder' -Recurse |
Where-Object { $_.BaseName -ne $_.BaseName.Trim() } |
ForEach-Object {
# the DirectoryName property does not exist on DirectoryInfo objects..
$path = [System.IO.Path]::GetDirectoryName($_.FullName)
$trimmed = Join-Path -Path $path -ChildPath ('{0}{1}' -f $_.BaseName.Trim(), $_.Extension.Trim())
$_.MoveTo($trimmed)
}

Unable to unzip using powershell

I am trying to unzip using powershell with below command --
powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('E:\test.zip', 'E:\'); }"
I get below log
'E:\test1.txt'
already exists."
At line:1 char:53
+ & { Add-Type -A 'System.IO.Compression.FileSystem';
[IO.Compression.ZipFile]::Ex ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IOException
===============================
I don't see the archive being unzipped.
Update 1
E:\test1.txt already exists at the destination. How to change the command to overwrite files.
Update 2
The version of powershell available doesn't support Expand-Archive
You can't overwrite files with that method. You need to read the documentation for ZipFileExtensions.ExtractToDirectory(source, destinationDirectoryName):
This method creates the directory specified by
destinationDirectoryName. If the destination directory already exists, this method does not overwrite it; it throws an IOException
exception. The method also creates subdirectories that reflect the
hierarchy in the zip archive. If an error occurs during extraction,
the archive remains partially extracted. Each extracted file has the
same relative path to the directory specified by
destinationDirectoryName as its source entry has to the root of the archive.
If you want to use ZipFileExtensions.ExtractToDirectory() with overwrite, you'll need to extract the files to a temporary folder and then copy/move them to the desired location.
Try something like:
do {
$TempFolder = Join-Path -Path $([System.IO.Path]::GetTempPath()) -ChildPath $([System.IO.Path]::GetRandomFileName());
} while ((Test-Path -Path $TempFolder));
mkdir $TempFolder | Out-Null;
[IO.Compression.ZipFile]::ExtractToDirectory('E:\test.zip',$TempFolder);
Get-ChildItem -Path $TempFolder -Recurse | Move-Item -Destination 'E:\' -Force;
rmdir $TempFolder;
Note that this code is untested.
Below is what solved my problem
md E:\temp
move E:\test.zip E:\temp
powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('E:\temp\test.zip', 'E:\temp\'); }"
del E:\temp\test.zip
move /Y E:\temp\* E:\
rd E:\temp

Delete broken link

I need to delete all the content of a folder which may include broken links among others. The folder path is provided by a variable. Problem is that PowerShell fails to remove the broken links.
$folderPath = "C:\folder\"
Attempt 1:
Remove-Item -Force -Recurse -Path $folderPath
Fails with error:
Remove-Item : Could not find a part of the path 'C:\folder\brokenLink'.
At line:1 char:1
+ Remove-Item -Force -Recurse -Path $folderPath
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\folder\brokenLink:String) [Remove-Item], DirectoryNot
FoundException
+ FullyQualifiedErrorId : RemoveItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Attempt 2:
Start-Job { cmd /c rmdir $folderPath }
Fails because $folderPath gets passed as is instead of its value:
Start-Job { cmd /c rmdir $folderPath } | select command
Command
-------
cmd /c rmdir $folderPath
Any suggestion besides using the .NET framework?
EDIT
By broken link I'm referring to a folder which points to a previously mounted partition, that doesn't exist anymore. The folder is still available but when attempting to navigate into it this error occurs because the destination doesn't exist anymore:
Error:
C:\folder\brokenLink refers to a location that is unavailable. It
could be on a hard drive on this computer, or on a network. Check to
make sure that the disk is properly inserted, or that you are
connected to the Internet or your network, and then try again. If it
still cannot be located, the information might have been moved to a
different location.
This will work:
$folderPath = "C:\folderContaingBrokenSymlinks\";
$items = ls $folderPath -Recurse -ea 0;
foreach($item in $items){
if($item.Attributes.ToString().contains("ReparsePoint")){
cmd /c rmdir $item.PSPath.replace("Microsoft.PowerShell.Core\FileSystem::","");
}
else{
rm -Force -Recurse $item;
}
}

Parsing Shortcuts in Powershell

I have some code which is trying to make a copy of a directory which contains shortcuts:
# Create a directory to store the files in
mkdir "D:\backup-temp\website.com files\"
# Search for shortcuts so that we can exclude them from the copy
$DirLinks = Get-ChildItem "\\web1\c$\Web Sites\website\" -Recurse | ? { $_.Attributes -like "*ReparsePoint*" } | % { $_.FullName }
# Execute the copy
cp -recurse -Exclude $DirLinks "\\web1\c$\Web Sites\website\*" "D:\backup-temp\website.com files\"
But when I execute the script I get the following error:
Copy-Item : The symbolic link cannot be followed because its type is disabled.
At C:\scripts\backup.ps1:16 char:3
+ cp <<<< -recurse "\\web1\c$\Web Sites\website\*" "D:\backup-temp\website.com files\"
+ CategoryInfo : NotSpecified: (:) [Copy-Item], IOException
+ FullyQualifiedErrorId :
System.IO.IOException,Microsoft.PowerShell.Commands.CopyItemCommand
It seems the script is getting hung up on a symbolic link (I'm assuming the shortcut) that I'm trying to exclude in the fourth line of the script.
How can I tell powershell to ignore/exclude shortcuts?
Thanks,
Brad
If you are on V3 or higher you can eliminate the reparse points like so:
Get-ChildItem "\\web1\c$\Web Sites\website" -Recurse -Attributes !ReparsePoint |
Copy-Item -Dest "D:\backup-temp\website.com files"
On V1/V2 you can do this:
Get-ChildItem "\\web1\c$\Web Sites\website" |
Where {!($_.Attributes -bor [IO.FileAttributes]::ReparsePoint)} |
Copy-Item -Dest "D:\backup-temp\website.com files" -Recurse
So it turns out that the issue I faces is explained in this Microsoft Blog Post:
http://blogs.msdn.com/b/junfeng/archive/2012/05/07/the-symbolic-link-cannot-be-followed-because-its-type-is-disabled.aspx
Essentially on the server I am running the powershell script from I needed to run the following command:
fsutil behavior set SymlinkEvaluation R2R:1
This allows Remote to remote symbolic links. Once this is in place the above powershell commands run as expected without errors.

Delete directory regardless of 260 char limit

I'm writing a simple script to delete USMT migration folders after a certain amount of days:
## Server List ##
$servers = "Delorean","Adelaide","Brisbane","Melbourne","Newcastle","Perth"
## Number of days (-3 is over three days ago) ##
$days = -3
$timelimit = (Get-Date).AddDays($days)
foreach ($server in $servers)
{
$deletedusers = #()
$folders = Get-ChildItem \\$server\USMT$ | where {$_.psiscontainer}
write-host "Checking server : " $server
foreach ($folder in $folders)
{
If ($folder.LastWriteTime -lt $timelimit -And $folder -ne $null)
{
$deletedusers += $folder
Remove-Item -recurse -force $folder.fullname
}
}
write-host "Users deleted : " $deletedusers
write-host
}
However I keep hitting the dreaded Remove-Item : The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
I've been looking at workarounds and alternatives but they all revolve around me caring what is in the folder.
I was hoping for a more simple solution as I don't really care about the folder contents if it is marked for deletion.
Is there any native Powershell cmdlet other than Remove-Item -recurse that can accomplish what I'm after?
I often have this issue with node projects. They nest their dependencies and once git cloned, it's difficult to delete them. A nice node utility I came across is rimraf.
npm install rimraf -g
rimraf <dir>
Just as CADII said in another answer: Robocopy is able to create paths longer than the limit of 260 characters. Robocopy is also able to delete such paths. You can just mirror some empty folder over your path containing too long names in case you want to delete it.
For example:
robocopy C:\temp\some_empty_dir E:\temp\dir_containing_very_deep_structures /MIR
Here's the Robocopy reference to know the parameters and various options.
I've created a PowerShell function that is able to delete a long path (>260) using the mentioned robocopy technique:
function Remove-PathToLongDirectory
{
Param(
[string]$directory
)
# create a temporary (empty) directory
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
$tempDirectory = New-Item -ItemType Directory -Path (Join-Path $parent $name)
robocopy /MIR $tempDirectory.FullName $directory | out-null
Remove-Item $directory -Force | out-null
Remove-Item $tempDirectory -Force | out-null
}
Usage example:
Remove-PathToLongDirectory c:\yourlongPath
This answer on SuperUser solved it for me: https://superuser.com/a/274224/85532
Cmd /C "rmdir /S /Q $myDir"
I learnt a trick a while ago that often works to get around long file path issues. Apparently when using some Windows API's certain functions will flow through legacy code that can't handle long file names. However if you format your paths in a particular way, the legacy code is avoided. The trick that solves this problem is to reference paths using the "\\?\" prefix. It should be noted that not all API's support this but in this particular case it worked for me, see my example below:
The following example fails:
PS D:\> get-childitem -path "D:\System Volume Information\dfsr" -hidden
Directory: D:\System Volume Information\dfsr
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs 10/09/2014 11:10 PM 834424 FileIDTable_2
-a-hs 10/09/2014 8:43 PM 3211264 SimilarityTable_2
PS D:\> Remove-Item -Path "D:\System Volume Information\dfsr" -recurse -force
Remove-Item : The specified path, file name, or both are too long. The fully qualified file name must be less than 260
characters, and the directory name must be less than 248 characters.
At line:1 char:1
+ Remove-Item -Path "D:\System Volume Information\dfsr" -recurse -force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (D:\System Volume Information\dfsr:String) [Remove-Item], PathTooLongExcepti
on
+ FullyQualifiedErrorId : RemoveItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
PS D:\>
However, prefixing the path with "\\?\" makes the command work successfully:
PS D:\> Remove-Item -Path "\\?\D:\System Volume Information\dfsr" -recurse -force
PS D:\> get-childitem -path "D:\System Volume Information\dfsr" -hidden
PS D:\>
If you have ruby installed, you can use Fileman:
gem install fileman
Once installed, you can simply run the following in your command prompt:
fm rm your_folder_path
This problem is a real pain in the neck when you're developing in node.js on Windows, so fileman becomes really handy to delete all the garbage once in a while
This is a known limitation of PowerShell. The work around is to use dir cmd (sorry, but this is true).
http://asysadmin.tumblr.com/post/17654309496/powershell-path-length-limitation
or as mentioned by AaronH answer use \?\ syntax is in this example to delete build
dir -Include build -Depth 1 | Remove-Item -Recurse -Path "\\?\$($_.FullName)"
If all you're doing is deleting the files, I use a function to shorten the names, then I delete.
function ConvertTo-ShortNames{
param ([string]$folder)
$name = 1
$items = Get-ChildItem -path $folder
foreach ($item in $items){
Rename-Item -Path $item.FullName -NewName "$name"
if ($item.PSIsContainer){
$parts = $item.FullName.Split("\")
$folderPath = $parts[0]
for ($i = 1; $i -lt $parts.Count - 1; $i++){
$folderPath = $folderPath + "\" + $parts[$i]
}
$folderPath = $folderPath + "\$name"
ConvertTo-ShortNames $folderPath
}
$name++
}
}
I know this is an old question, but I thought I would put this here in case somebody needed it.
There is one workaround that uses Experimental.IO from Base Class Libraries project. You can find it over on poshcode, or download from author's blog. 260 limitation is derived from .NET, so it's either this, or using tools that do not depend on .NET (like cmd /c dir, as #Bill suggested).
Combination of tools can work best, try doing a dir /x to get the 8.3 file name instead. You could then parse out that output to a text file then build a powershell script to delete the paths that you out-file'd. Take you all of a minute. Alternatively you could just rename the 8.3 file name to something shorter then delete.
For my Robocopy worked in 1, 2 and 3
First create an empty directory lets say c:\emptydir
ROBOCOPY c:\emptydir c:\directorytodelete /purge
rmdir c:\directorytodelete
This is getting old but I recently had to work around it again. I ended up using 'subst' as it didn't require any other modules or functions be available on the PC this was running from. A little more portable.
Basically find a spare drive letter, 'subst' the long path to that letter, then use that as the base for GCI.
Only limitation is that the $_.fullname and other properties will report the drive letter as the root path.
Seems to work ok:
$location = \\path\to\long\
$driveLetter = ls function:[d-z]: -n | ?{ !(test-path $_) } | random
subst $driveLetter $location
sleep 1
Push-Location $driveLetter -ErrorAction SilentlyContinue
Get-ChildItem -Recurse
subst $driveLetter /D
That command is obviously not to delete files but can be substituted.
PowerShell can easily be used with AlphaFS.dll to do actual file I/O stuff
without the PATH TOO LONG hassle.
For example:
Import-Module <path-to-AlphaFS.dll>
[Alphaleonis.Win32.Filesystem.Directory]::Delete($path, $True)
Please see at Codeplex: https://alphafs.codeplex.com/ for this .NET project.
I had the same issue while trying to delete folders on a remote machine.
Nothing helped but... I found one trick :
# 1:let's create an empty folder
md ".\Empty" -erroraction silentlycontinue
# 2: let's MIR to the folder to delete : this will empty the folder completely.
robocopy ".\Empty" $foldertodelete /MIR /LOG+:$logname
# 3: let's delete the empty folder now:
remove-item $foldertodelete -force
# 4: we can delete now the empty folder
remove-item ".\Empty" -force
Works like a charm on local or remote folders (using UNC path)
Adding to Daniel Lee's solution,
When the $myDir has spaces in the middle it gives FILE NOT FOUND errors considering set of files splitted from space. To overcome this use quotations around the variable and put powershell escape character to skip the quatations.
PS>cmd.exe /C "rmdir /s /q <grave-accent>"$myDir<grave-accent>""
Please substitute the proper grave-accent character instead of <grave-accent>
SO plays with me and I can't add it :). Hope some one will update it for others to understand easily
Just for completeness, I have come across this a few more times and have used a combination of both 'subst' and 'New-PSDrive' to work around it in various situations.
Not exactly a solution, but if anyone is looking for alternatives this might help.
Subst seems very sensitive to which type of program you are using to access the files, sometimes it works and sometimes it doesn't, seems to be the same with New-PSDrive.
Any thing developed using .NET out of the box will fail with paths too long. You will have to move them to 8.3 names, PInVoke (Win32) calls, or use robocopy