How to follow a shortcut in powershell - powershell

In powershell, you use cd dir to go into the directory dir.
But if dir is a shortcut to a directory, cd dir and cd dir.lnk both give an error, saying that the directory doesn't exist.
So how do I follow that shortcut?
(In Linux cd dir just works. In Windows, I've got no idea)

Using the shell com-object, you can get the target path and from there, do what you wish. Get-ShortcutTargetPath
function Get-ShortcutTargetPath($fileName) {
$sh = New-Object -COM WScript.Shell
$targetPath = $sh.CreateShortcut($fileName).TargetPath
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sh) | Out-Null
return $targetPath
}
$file = 'Path\to\Filename.lnk'
$TargetPath = Get-shortcutTargetPath($file)
if (Test-Path -PathType Leaf $TargetPath) {
$TargetPath = Split-Path -Path $TargetPath
}
Set-Location $TargetPath

The other answers don't actually work for me, the first does nothing and the second gives me the error "The shortcut pathname must end with .lnk or .url."
However, after digging through MSDN for a while I worked out a solution. I have no idea why I can't find any answers to this on Google, but I even travelled to the untouched second and third pages and tried multiple keywords... I guess the search results are just buried for some reason. But here's how I solved the problem. Using a while loop and the Get-Item command, you can follow a symbolic link to it's true path! Works for directory links, too.
So far the double-cd approach is the most reliable/universal I can find for handling a situation of mixed relative/absolute paths in the filenames and targets, and I tested several scenarios.
function getLinkTarget($fn) {
$op=$PWD #Save original path
while($t=(Get-Item $fn).Target) { #Get link target
cd (Split-Path -Parent $fn) #cd to parent of file/dir
cd (Split-Path -Parent $t) #cd again to parent of target
$fn=(Split-Path -Leaf $t) #Set filename to relative target
}
$fn=(Join-Path $PWD $fn) #Make path absolute
cd $op #Change back to original path
return $fn
}

In Windows 10 cd directory_name or cd dir* if you only want part of the name assuming no other directories start with the same dir*.

Related

Copying files to directory whilst retaining directory structure from list

Good afternoon all,
I'm guessing this is super easy but really annoying for me; I have a text file with a list of files, in the same folders there are LOTS of other files but I only need specific ones.
$Filelocs = get-content "C:\Users\me\Desktop\tomove\Code\locations.txt"
Foreach ($Loc in $Filelocs){xcopy.exe $loc C:\Redacted\output /s }
I figured this would go through the list which is like
"C:\redacted\Policies\IT\Retracted Documents\Policy_Control0.docx"
and then move and create the folder structure in a new place and then copy the file, it doesn't.
Any help would be appreciated.
Thanks
RGE
xcopy can't know the folder structure when you explicitly pass source file path instead of a source directory. In a path like C:\foo\bar\baz.txt the base directory could be any of C:\, C:\foo\ or C:\foo\bar\.
When working with a path list, you have to build the destination directory structure yourself. Resolve paths from text file to relative paths, join with destination directory, create parent directory of file and finally use PowerShell's own Copy-Item command to copy the file.
$Filelocs = Get-Content 'locations.txt'
# Base directory common to all paths specified in "locations.txt"
$CommonInputDir = 'C:\redacted\Policies'
# Where files shall be copied to
$Destination = 'C:\Redacted\output'
# Temporarily change current directory -> base directory for Resolve-Path -Relative
Push-Location $CommonInputDir
Foreach ($Loc in $Filelocs) {
# Resolve input path relative to $CommonInputDir (current directory)
$RelativePath = Resolve-Path $Loc -Relative
# Resolve full target file path and directory
$TargetPath = Join-Path $Destination $RelativePath
$TargetDir = Split-Path $TargetPath -Parent
# Create target dir if not already exists (-Force) because Copy-Item fails
# if directory does not exist.
$null = New-Item $TargetDir -ItemType Directory -Force
# Well, copy the file
Copy-Item -Path $loc -Destination $TargetPath
}
# Restore current directory that has been changed by Push-Location
Pop-Location
Possible improvements, left as an exercise:
Automatically determine common base directory of files specified in "locations.txt". Not trivial but not too difficult.
Make the code exception-safe. Wrap everything between Push-Location and Pop-Location in a try{} block and move Pop-Location into the finally{} block so the current directory will be restored even when a script-terminating error occurs. See about_Try Catch_Finally.

Powershell Script "$dirs"

So I do not have a degree or any formal training in any programming language but my job has had me slowly learn the basics of SQL and I have now been given a new task at work. The previous person in charge of this task ran powershell scripts to combine and rename PDFs. I get the macro level of how this all works. The script sets a loop through the parent directory into all the children directory concatenates the PDFs using PDFtk Server then renames the combined PDFs to child directory they are named in. However, I cannot figure out how to specify the dirs (I think thats the term). As it stands now I can only successfully run the powershell script in the folder in which Console2 is located.
$path = Split-Path -parent $MyInvocation.MyCommand.Definition
Function mergeFiles
{
# Loop through all directories
$dirs = dir $path -Recurse | Where { $_.psIsContainer -eq $true }
$cmd = 'C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe'
$In1 = 'A.pdf'
$In2 = 'B.pdf'
$Out1 = 'C.pdf'
Foreach ($dir In $dirs)
This is the first part of the merge files function. Can someone help me figure out how to identify a specfic "dirs"? (Like if I had the PDF in a folder on my desktop)
The location that is populating the directory it will search based on the location of your script, based on this line:
$path = Split-Path -parent $MyInvocation.MyCommand.Definition
$MyInvocation.MyCommand.Definition is the full path to the running script and the command Split-Path -parent will return the parent directory. You could just change that line to be the the location you want i.e.:
$path = 'C:\Users\JC\Desktop\PDF'
but you probably don't want to hardcode that path. What you want to do is add the path as an input argument to the script. To do that, add the following to the top of your script:
PARAM($path)
Then when you invoke your script, you just pass the path you are interested in:
.\theScript.ps1 C:\Users\JC\Desktop\PDF
You can then get more advanced and specify a default value, for example, if you want the default to be the location the script is run:
PARAM($path = '.')

Create new absolute path from absolute path + relative or absolute path

I am working on a build script using psake and I need to create an absolute path from the current working directory with an inputted path which could either be a relative or absolute path.
Suppose the current location is C:\MyProject\Build
$outputDirectory = Get-Location | Join-Path -ChildPath ".\output"
Gives C:\MyProject\Build\.\output, which isn't terrible, but I would like without the .\. I can solve that issue by using Path.GetFullPath.
The problem arises when I want to be able to provide absolute paths
$outputDirectory = Get-Location | Join-Path -ChildPath "\output"
Gives C:\MyProject\Build\output, where I need C:\output instead.
$outputDirectory = Get-Location | Join-Path -ChildPath "F:\output"
Gives C:\MyProject\Build\F:\output, where I need F:\output instead.
I tried using Resolve-Path, but this always complains about the path not existing.
I'm assuming Join-Path is not the cmdlet to use, but I have not been able find any resources on how to do what I want. Is there a simple one-line to accomplish what I need?
You could use GetFullPath(), but you would need to use a "hack" to make it use you current location as the current Directory(to resolve relative paths). Before using the fix, the .NET method's current directory is the working directory for the process, and not the location you have specified inside the PowerShell process. See Why don't .NET objects in PowerShell use the current directory?
#Hack to make .Net methods use the shells current directory instead of the working dir for the process
[System.Environment]::CurrentDirectory = (Get-Location)
".\output", "\output", "F:\output" | ForEach-Object {
[System.IO.Path]::GetFullPath($_)
}
Output:
C:\Users\Frode\output
C:\output
F:\output
Something like this should work for you:
#Hack to make .Net methods use the shells current directory instead of the working dir for the process
[System.Environment]::CurrentDirectory = (Get-Location)
$outputDirectory = [System.IO.Path]::GetFullPath(".\output")
I don't think there's a simple one-liner. But I assume you need the path created anyway, if it doesn't exist yet? So why not just test and create it?
cd C:\
$path = 'C:\Windows', 'C:\test1', '\Windows', '\test2', '.\Windows', '.\test3'
foreach ($p in $path) {
if (Test-Path $p) {
(Get-Item $p).FullName
} else {
(New-Item $p -ItemType Directory).FullName
}
}

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

Copy directory structure with PowerShell

Is there a way of doing the following in PowerShell?
xcopy \\m1\C$\Online\*.config \\m2\C$\Config-Backup /s
I have tried this:
Copy-Item \\m1\C$\Online\* -Recurse -Destination \\m2\C$\Config-Backup -include *.config
But it does nothing, probably because there are no configuration files in the root. How do I do it?
If you would like to use native PowerShell (with a third party .NET module :P) and also don't want to let long file paths (> 255 characters) halt the copy, you can use this:
# Import AlphaFS .NET module - http://alphafs.codeplex.com/
Import-Module C:\Path\To\AlphaFS\DLL\AlphaFS.dll
# Variables
$SourcePath = "C:\Temp"
$DestPath = "C:\Test"
# RecursePath function.
Function RecursePath([string]$SourcePath, [string]$DestPath){
# for each subdirectory in the current directory..
[Alphaleonis.Win32.Filesystem.Directory]::GetDirectories($SourcePath) | % {
$ShortDirectory = $_
$LongDirectory = [Alphaleonis.Win32.Filesystem.Path]::GetLongPath($ShortDirectory)
# Create the directory on the destination path.
[Alphaleonis.Win32.Filesystem.Directory]::CreateDirectory($LongDirectory.Replace($SourcePath, $DestPath))
# For each file in the current directory..
[Alphaleonis.Win32.Filesystem.Directory]::GetFiles($ShortDirectory) | % {
$ShortFile = $_
$LongFile = [Alphaleonis.Win32.Filesystem.Path]::GetLongPath($ShortFile)
# Copy the file to the destination path.
[Alphaleonis.Win32.Filesystem.File]::Copy($LongFile, $LongFile.Replace($SourcePath, $DestPath), $true)
}
# Loop.
RecursePath $ShortDirectory $DestPath
}
}
# Execute!
RecursePath $SourcePath $DestPath
Please note this code was stripped out of a much larger project of mine, but I gave it a quick test and it seems to work. Hope this helps!
Start-Process xcopy "\\m1\C$\Online\*.config \\m2\C$\Config-Backup /s" -NoNewWindow
:P
The new AlphaFS 2.0 makes this really easy.
Example: Copy a directory recursively
# Set copy options.
PS C:\> $copyOptions = [Alphaleonis.Win32.Filesystem.CopyOptions]::FailIfExists
# Set source and destination directories.
PS C:\> $source = 'C:\sourceDir'
PS C:\> $destination = 'C:\destinationDir'
# Copy directory recursively.
PS C:\> [Alphaleonis.Win32.Filesystem.Directory]::Copy($source, $destination, $copyOptions)
AlphaFS on GitHub
Look into robocopy. It's not a native PowerShell command, but I call it from PowerShell scripts all the time. Works similarly to xcopy only it's way more powerful.