Use Powershell to delete ShellIconOverlayIdentifiers on Windows - windows

I am currently very annoyed by Dropbox and Nextcloud, which both battle the ShellIconOverlayIdentifier list. A problem which many people seem to have, when you search the internet.
Now I want to combine my annoyance with my intent to learn powershell (7.2.0).
I started with the following script, which shall retrieve all keys. And later I want to use regex via -match to find the entries I want to delete. For now I work with both Remove-Item -WhatIf and Get-ItemProperty to test it.
Currently my problem is that I can create my list as intended. But when I feed the list into the remove command I get that the path cannot be found. What am I doing wrong?
Push-Location -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers
$list = Get-ChildItem -Path .
$filteredList = $list -match "DropboxExt10"
$filteredList
# Remove-Item -WhatIf -Recurse $filteredList
Get-ItemProperty $filteredList
Pop-Location
The error is Cannot find path 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\ DropboxExt10' because it does not exist. Apparantly it adds the path as relative path to the current location. Why doesn't it interpret as an absolute path? When I ommit the push-location part it trys to add the registry path to my current working directory in which the script lives. But this is wrong as well.
Thanks for your help in advance.

Use one of the following:
$filteredList | Remove-Item -WhatIf -Recurse
# Alternatively:
Remove-Item -LiteralPath $filteredList.PSPath -WhatIf -Recurse
The above commands ensure that the registry items (keys) are bound by their full, provider-qualified paths to Remove-Item's -LiteralPath parameter, via their .PSPath property; in the case of registry paths, the path's provider prefix is Microsoft.PowerShell.Core\Registry:: (or, if identifying the originating module isn't also required, Registry::, as used in your Push-Location call)
What appears to be happening is that when the items are stringified - which is what happens when you pass them as a whole, as an argument (e.g. Remove-Item $filteredlist, which is the same as Remove-Item -Path $filteredlist), they lack the provider prefix and are represented as registry-native paths.
And given that a full registry-native path such as HKEY_LOCAL_MACHINE\... neither starts with a drive specification nor with \ or /, it is interpreted as a relative path, resulting in the symptom you saw.

Related

Attempting to search and replace a few lines in an XML file in every user's appdata/roaming folder

I'm still kind of new to windows administration and very new to powershell so please forgive my ignorance. I wrote the following one-liner and I'm attempting to adapt it so that it can modify every existing user's FTRSettings.xml file on a PC. I'm worried that using the following path with a wildcard in place of the username would only replace the first file it finds.
C:\users\*\appdata\Roaming\FTR\GoldMain\FTRSettings.xml
Tested and working one liner executed from the appdata\Roaming\FTR\GoldMain\ directory.
((Get-Content -Path .\FTRSettings.xml -Raw) -replace '<Setting name="audioLevelsVisible" type="11">0</Setting>','<Setting name="audioLevelsVisible" type="11">-1</Setting>' -replace '<Setting name="inputLevelsVisible" type="11">0</Setting>','<Setting name="inputLevelsVisible" type="11">-1</Setting>' -replace '<Setting name="logSheetPaneViewState" type="11">0</Setting>','<Setting name="logSheetPaneViewState" type="11">-1</Setting>')
I was wondering if anyone could point me in the right direction.
To process each file individually:
Use Get-ChildItem with your wildcard-based path to find all matching files.
Loop over all matching files with ForEach-Object and perform the string replacement and updating for each file.
Note: I'm using a single -replace operation for brevity.
Get-ChildItem -Path C:\users\*\appdata\Roaming\FTR\GoldMain\FTRSettings.xml |
ForEach-Object {
$file = $_
($file | Get-Content -Raw) -replace 'foo', 'bar' |
Set-Content -Encoding utf8 -LiteralPath $file.FullName -WhatIf
}
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note the use of -Encoding to control the character encoding explicitly, because the original encoding is never preserved in PowerShell; instead, the cmdlet's own default applies - adjust as needed.

powershell copy-item doesn't copy when filter is used

I'm trying to copy files using copy-item. Specifically, I want to copy files with a particular extension that are within a folder or its subfolders to another location, and to retain the subfolder hierarchy. I've tried using -filter and -include to specify the file extension, but no files are copied.
My source and destination paths are stored in variables $packageSourcePath and $objPath. When called, $packageSourcePath will be like the following ".\src\projects\Project1\PackageFiles" and $objPath will be like the following ".\bld\Project1\obj".
The command I've tried using is this:
Copy-Item -Path $packageSourcePath\* -Filter *.resw -Destination $objPath -Recurse
I've also tried variations, such as leaving off * from the path, or using -Include instead of -Filter. Nothing works. If I leave out the -Filter argument, then files copy, but all of the files are copied. I only want files with the particular extension.
I've given up on Copy-Item. JohnLBevan's answer didn't actually do what I want since all files in the source root get copied, even though they don't match the filter. I tried piping Convert-Path | Select-String | Copy-Item but still got all files in the source root being copied.
A contact in a different context provided a couple of suggestions:
1)
Get-ChildItem -Force -Recurse -ErrorAction Ignore -Path $packageSourcePath -Filter *.resw | % {
$src = $_.FullName
$dst = Join-Path $objPath $src.SubString($packageSourcePath.Length)
echo "copy ""$src"" ""$dst"""
}
I think this is a bit harder to follow, hence less maintainable for the next person (likely another PS-neophyte like me) a year from now. ("Why is the -ErrorAction parameter needed here? What's the behaviour of the Substring() method, and why can't I find that using Get-Help?")
This suggestion is a bit clearer, after re-familiarizing with attrib and checking the effect of the xcopy switches:
2)
cd $packageSourcePath
attrib -a /s
attrib +a *.resw /s
xcopy /eidlm $packageSourcePath $objPath
But if we're going to use xcopy, we don't need to call attrib:
xcopy $packageSourcePath*.resw $objPath /s /i > $null
The only problem with this for my scenario is that xcopy emits an error if no matching files are found. My script is being used for a VSTS build task, and the xcopy errors cause the build task to fail. (For that reason, I'm guess that suggestion 2 also wouldn't work for me.)
So, I've opted for this:
# In PS version 5.1, nothing gets copied using Copy-Item $packageSourcePath\* -Filter *.resw ...
# so resorting to using xcopy, which mostly works. The one issue is that xcopy will output an
# error if no matching file is found, so using GCI first to test for a matching file.
if ($(Get-ChildItem $packageSourcePath\*.resw -Recurse).count -gt 0) {
xcopy $packageSourcePath\*.resw $objPath /s /i > $null
}
The condition using GCI is added to check there are matching files before calling xcopy, thereby avoiding any errors.
I'm still amazed that Copy-Item -Filter -Recurse didn't work.
This should do it (obviously this could be done in 1 line; I've assigned values to the variables just to help make it readable / self-explanatory):
[string]$filter = '*.resw'
[string]$source = Join-Path -Path $packageSourcePath -ChildPath '*'
[string]$target = $objPath
$source | Convert-Path | Copy-Item -Filter $filter -Recurse -Destination $target -Container #-Force
Notes:
We append the asterisk to the source path to ensure that we copy the contents of the source folder to the destination, without copying the source's root folder into the destination (i.e. say we're copying c:\temp\from to c:\temp\to, we don't want c:\temp\to\from (unless it's a copy of c:\temp\from\from)).
We use the Join-Path cmdlet to append this asterisk to ensure the appropriate slashes are inserted into the path.
We do a Convert-Path on the source to resolve the asterisk to the child folder/file names... for some reason copy-item doesn't handle these asterisks well. NB: Convert-Path will potentially return an array of paths; i.e. if there's more than one file/subfolder directly under the source folder. Get-Item or Resolve-Path could equally be used for this; I prefer Convert-Path since it returns a simple string array, rather than a more complex type; but there's no strong argument for using any one over the others.
We pipe these source paths to the Copy-Item command so it can be applied to each path returned by Convert-Path.
We include -Recurse to say we're interested in anything in the subfolders of the copied path.
We include the -Container parameter to say that we want to preserve any folder structure when copying. Strictly this is not needed, as this switch is defaulted to true (i.e. rather we should specify if we don't want this behaviour: -Container:$false; but I like to be clear that I deliberately want to preserve the directory structure, as opposed to leaving the assumption that I may not have thought of this. There's a better explanation of this here: https://stackoverflow.com/a/21798660/361842.
You could optionally include -Force; this would mean that should an item of the same name already exist in the target we overwrite it instead of getting an error.
Related documentation:
Join-Path
Convert-Path
Copy-Item
Update 2018-01-03
Per comments, this solution should ensure that only those items you want get copied, and pre-existing directories shouldn't cause issues.
[string]$filter = '*.resw'
[string]$source = $packageSourcePath
[string]$target = $objPath
#copy all files in subfolders of the source
$source | Get-ChildItem -Directory | Copy-Item -Filter $filter -Recurse -Destination $target -Container -Force
#copy all files in root of the source
$source | Get-ChildItem -File -Filter $filter | Copy-Item -Destination $target -Container -Force
This solution uses 2 steps; there's probably a better option, but due to the peculiarities / bug in this cmdlet the above's a reliable option.

Powershell renaming special directories recursively

I got this folder structure
C:\Users\myUser\Desktop
including folders called
BL-100
BL-105
BL-108
and so on...
most BL-folders storing a file.xml, but not all.
So on Desktop are much folders starting with BL- and the most, but not all, storing a file.xml.
Now I want to search all folders which are starting with BL- and store a file.xml and rename those folders to RG-100, RG-105, RG-108 and so on
At the moment I got this script:
foreach($Directory in Get-ChildItem -Path C:\Users\myUser\Desktop -Recurse | Where-Object{($_.Name.Substring(0,3) -eq 'BL-')}){
}
This does not work and is showing me error: Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string. Parameter name: length"
Anyone can help please?
The error you're seeing is because SubString fails for some reason. The most likely reason would be if the string is not long enough; e.g. if you had a folder with a 1 character long name. To see what I mean, try running: '1'.Substring(0,2).
To avoid this, instead you could use the like operator. e.g.
foreach($Directory in (
Get-ChildItem -LiteralPath 'C:\Users\myUser\Desktop' -Recurse `
| Where-Object{($_.Name -like 'BL-*')}
)){
#...
}
Just do it:
Get-ChildItem "C:\Users\myuser\Desktop" -directory -Filter "BL-*"

How to use PowerShell Remove-Item to delete a directory with long name? [duplicate]

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

Move list of empty folders from one volume to another with PowerShell

I am using PowerShell 2.0 on a Windows SBS 2008 machine with the latest service packs. I have a two line script that finds all empty folders in a directory:
$a = Get-ChildItem E:\File_Server -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | [what now?!]
The second line finds all folders that are empty, however I'm stumped on the last pipe. Here's what I've tried so far (keep in mind that the following were tried after the second pipe in the second line above):
$_.move(F:\path) Yes, I tried that. Yes, I'm a PowerShell noob. Of course, I received the error "Expressions are only allowed as the first element of a pipeline."
move-item -destination F:\Path I receive the lamest error ever: "Move-Item : Source and destination path must have identical roots. Move will not work across volumes." Seriously? What kind of asinine limitation is that?! Moving on...
copy-item -destination F:\empty_folders Apparently I can get around the limitation of move-item by using copy-item and then use remove-item. No such luck, however. I started the script with copy-item first. PowerShell didn't throw any errors, but also didn't do any thing else. It just sat there for the better part of an hour. No directories were moved.
Summary:
How do I take the list of empty folders that I have and, using PowerShell, move those empty directories elsewhere (across volumes!) deleting the originals after the move? Notice the caveat "using PowerShell" as I've already thought about adding RoboCopy to the mix but would like to keep it within PowerShell.
I was pretty frustrated with this as well, but like you said copy-item followed by remove-item will work:
gci e:\file_server | ?{$_.PSIsContainer -eq $true} | ?{$_.GetFiles().count -eq 0} |
%{copy-item -LiteralPath $_.fullname -Destination f:\path; remove-item $_.fullname}
Note this is not just a limitation with Move-Item and Powershell but applicable to .Net System.IO.Directory.Move and to be fair, the documentation says so:
Move-Item will move files between drives that are supported by the
same provider, but it will move directories only within the same
drive.
http://technet.microsoft.com/en-us/library/dd315310.aspx
PS: You got the "Expressions are only allowed as the first element of a pipeline." error because the $_.move(F:\path) that you were trying must be inside a foreach-object like so - %{$_.move(F:\path)}

Resources