Copy directory structure with PowerShell - 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.

Related

Install an .MSI file from the same directory as the powershell script without setting a static file path

I have a script that we use that is in Powershell however I need the script to be able to find the files that it needs to install an application dynamically as users can copy the folder to any folder on their computer. Currently I have the below code set, I guess my question is, if the script is in the same folder as the install files. How do I tell powershell to just look in the directory that its being ran from for the install files?
$Install = "C:\Other Folder\File.msi"
$InstallArg = "/quite /norestart"
Start-Process '
-FilePath $Install '
-ArgumentList $InstallArg '
-PassThru | Wait-Process
Any help would be appreciated. Thank you.
Update, I found that I have to be in the directory the script is in. However since we have to run ISE with admin credentials it automatically defaults to C:\Windows\System32 as the directory powershell is looking in regardless if I tell it to open the script. If that is the case how can I tell it to look where the script is located so that it can find the files that it needs?
I have found my answer below is how I got it to work with our current situation. Thank you Thomas for the help!
$ScriptLocation = Get-ChildItem -Path C:\Users -Filter Untitled2.ps1 -Recurse | Select Directory
cd $ScriptLocation.Directory
$Install = ".\Install.msi"
$InstallArg = "/quite /norestart"
Start-Process '
-FilePath $Install '
-ArgumentList $InstallArg '
-PassThru | Wait-Process
Define a relative path:
$Install = ".\File.msi"
If you are not running the script inside the directory, where the script itself and the executable are stored, you will have to determine the absolute path to it. With PowerShell 3+ you can easily determine the directory, where your script is stored, using the $PSScriptRoot variable. You could define your $Install variable like this:
$Install = Join-Path -Path $PSScriptRoot -ChildPath "File.msi"
Thank you so much for your help everyone! I accidentally stumbled upon the perfect solution.
# Set active path to script-location:
$path = $MyInvocation.MyCommand.Path
if (!$path) {
$path = $psISE.CurrentFile.Fullpath
}
if ($path) {
$path = Split-Path $path -Parent
}
Set-Location $path

PowerShell Move-Item Folder Time Stamp

I'm moving some folders and all their content with PowerShell Move-Item, but when I moved the folder its date modified is the current date not the original. How can I move the folder without changing its last modified date.
Here's a powershell function that'll do what you're asking... It does absolutely no sanity checking, so caveat emptor...
function Move-FileWithTimestamp {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true,Position=0)][string]$Path,
[Parameter(Mandatory=$true,Position=1)][string]$Destination
)
$origLastAccessTime = ( Get-Item $Path ).LastAccessTime
$fileName = ( Get-Item $Path ).Name
Move-Item -Path $Path -Destination $Destination
$(Get-Item ($Destination+'\'+$fileName)).LastAccessTime = $origLastAccessTime
}
Once you've run loaded that, you can do something like:
Move-FileWithTimestamp c:\foo.txt c:\newFolder
There's no simple way to do this in windows but you can use robocopy. There is an option to copy the modified date attribute.
Run help on robocopy (robocopy /?) and look for /COPY and /COPYALL options.
This only works in Vista and newer Windows versions.

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

How do I make a powershell function that expands filename arguments to include their paths if unqualified?

Starting up notepad++ or many other GUI applications in Windows that will accepted fully qualified filenames of documents on the command line, but which do not accept them if they are not fully qualified, is often done in DOS/Windows batch files like this:
#echo off
start "notepad++" "C:\Program Files (x86)\Notepad++\notepad++.exe" %*
The above, if saved as "npp.cmd" will let you type "npp foo.txt" and it will work.
Note that without the npp.cmd, even typing out the full path to the exe, but not fully qualifying the file to edit doesn't work, like this:
"C:\Program Files (x86)\Notepad++\notepad++.exe" foo.txt
This however, DOES work:
"C:\Program Files (x86)\Notepad++\notepad++.exe" c:\users\warren\foo.txt
A way to easily work around this limitation is to make a batch file (.cmd) as shown at the top of this file. I'm learning PowerShell and trying to find the equivalent magic to the "start .... %*" incantation in the batchfile at the top. I believe it would have to be a 'powershell function'.
Here's what I have so far:
new-item -path alias:nppapp -value "C:\Program Files (x86)\Notepad++\notepad++.exe"
function npp { nppapp $args }
The above is equivalent, in the end to simply an alias, because $args is really not equivalent to %*, in that it does not do parameter expansion. I think I need something like this:
new-item -path alias:nppapp -value "C:\Program Files (x86)\Notepad++\notepad++.exe"
function npp { nppapp globexpand($args) }
globexpand is of course, a placeholder, for some kind of expansion/globbing routine, which I haven't been able to find yet in PowerShell.
try this:
new-item -path alias:nppapp -value "C:\Program Files (x86)\Notepad++\notepad++.exe"
function npp { nppapp (join-path -Path $pwd -ChildPath $args[0]) }
$pwd is an automatic variable with the current working path as value
Edit:
function npp {
if ($args[0] -match '.:\\.+')
{
nppapp $args[0]
}
else
{
nppapp (join-path -Path $pwd -ChildPath $args[0]) }
}

PowerShell: how to copy only selected files from source directory?

I'm a Powershell newbie, trying to get a simple script to run.
I have a list of files that I want to copy from some src_dir to a dst_dir. I wrote a simple script (which is obviously wrong since it didnt do anything when I executed it).
Could someone please help check to see what I'm doing wrong?
# source and destionation directory
$src_dir = "C:\Users\Pac\Desktop\C4new"
$dst_dir = "C:\Users\Pac\Desktop\csci578-hw3\Prism\C4new"
# list of files from source directory that I want to copy to destination folder
# unconditionally
$file_list = "C4newArchitecture.java", "CustomerData.java"
# Copy each file unconditionally (regardless of whether or not the file is there
for($i=0; $i -le $file_list.Length - 1; $i++)
{
Copy-Item -path $src_dir+$file_list[$i] -dest $dst_dir -force
}
Assuming that the files are directly unde $src_dir you can do this a bit more simply ie the copy can be a one-liner:
$file_list | Copy-Item -Path {Join-Path $src_dir $_} -Dest $dst_dir -ea 0 -Whatif
-ea is the alias for the -ErrorAction parameter and 0 value corresponds to SilentlyContinue. This causes the Copy-Item to ignore errors like you would get if one of the source files didn't exist. However, if you are running into problems temporarily remove this parameter so you can see the error messages.
When typing this stuff interactively I tend to use shortcuts like this but in scripts it is better to spell it out for readability. Also notice that the -Path parameter can take a scriptblock i.e. script in curly braces. Technically the Copy-Item cmdlet doesn't ever see the scriptblock, just the results of its execution. This works in general for any parameter that takes pipeline input. Remove the -WhatIf to have the command actually execute.
Oh..that was actually pretty easy:
# source and destionation directory
$src_dir = "C:\Users\Pac\Desktop\C4new\"
$dst_dir = "C:\Users\Pac\Desktop\csci578-hw3\Prism\C4new"
# list of files from source directory that I want to copy to destination folder
# unconditionally
$file_list = "C4newArchitecture.java",
"CustomerData.java",
"QuickLocalTransState.java",
"QuickLocalTransState_AbstractImplementation.java",
"SaveSessionOK.java",
"SessionID.java",
"UserInterface.java",
"UserInterface_AbstractImplementation.java"
# Copy each file unconditionally (regardless of whether or not the file is there
foreach ($file in $file_list)
{
Copy-Item $src_dir$file $dst_dir
}
As a programmer, I love the foreach!