Copy files from my current directory to new directory - powershell

so I am trying to copy 2 files from same folder that my Powershell script is in. I have created script there and also 2 files Moveit1.txt, Moveit2.txt my script is this :
$ScriptDirectory = get-childitem -path $PSScriptRoot
Copy-Item $ScriptDirectory\MoveIt1.txt $env:USERPROFILE + "\AppData\Roaming\" -Force
Copy-Item $ScriptDirectory\MoveIt2.txt "C:\Program Files (x86)\" -Force
But unfortunately it says the files can't be found? But if I check just line $ScriptDirectory it shows where its located and with the files inside. What am I doing wrong?

There is one thing to note:
$ScriptDirectory = Get-ChildItem -Path $PSScriptRoot
$scriptDirectory will most likely contain the 2 MoveIt files in addition to your .ps1 script. When you do:
$ScriptDirectory\MoveIt1.txt
I'm guessing it will end up being something like this when interpreted by Copy-Item:
path\to\script\script.ps1\path\to\script\moveit1.txt\path\to\script\moiveit2.txt\moveit1.txt
Try doing this instead:
Copy-Item (Join-Path $PSScriptRoot MoveIt1.txt) (Join-Path $env:USERPROFILE "\AppData\Roaming\") -Force
Copy-Item (Join-Path $PSScriptRoot MoveIt2.txt) "C:\Program Files (x86)\" -Force
Regarding your Access Denied issue and it working when ran as Administrator with hard-coded paths. You can put this at the top of the script so it elevates itself however this will pop an UAC prompt:
$invocation = "-File `"$PSCommandPath`""
Start-Process powershell -Verb Runas -ArgumentList $invocation

The primary problem with your code is a syntax problem (Santiago's helpful answer addresses additional problems):
In order to pass an expression such as
$env:USERPROFILE + "\AppData\Roaming\"
as a command argument, you need to enclose it in (...).
Neglecting to do so passes three arguments, as the following simplified example demonstrates:
# !! WRONG: Passes *three* arguments instead of the result
# of the intended expression.
PS> Write-Output $env:USERPROFILE + "\AppData\Roaming"
C:\Users\jdoe
+
\App\Data\Roaming
# OK: (...) forces a new parsing context, in which the expression is recognized
# as such.
PS> Write-Output $env:USERPROFILE + "\AppData\Roaming"
C:\Users\jdoe\App\Data\Roaming
As an aside:
You could use $env:APPDATA to directly get the path of interest, and
Even in cases where you do need to build a path from multiple strings, it may be simpler to use an expandable string instead of the + operator: "$env:USERPROFILE\AppData\Roaming" - in this particular case, because the string contains neither spaces nor other special characters (other than the intended $), the double quotes are even optional.
See this answer for more information.

try {
$scriptPath = $PSScriptRoot
if (!$scriptPath)
{
if ($psISE)
{
$scriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath
} else {
Write-Host -ForegroundColor Red "Cannot resolve script file's path"
exit 1
}
}
} catch {
Write-Host -ForegroundColor Red "Caught Exception: $($Error[0].Exception.Message)"
exit 2
}
Write-Host "Path: $scriptPath"
Copy-Item $ScriptPath\onexcuiconcif.xml -Destination "$env:APPDATA\Avaya\Avaya one-x Communicator" -Force
Copy-Item $scriptPath\InstallConfig.xml -Destination "C:\Program Files (x86)\Avaya\Avaya one-X Communicator" -Force

Related

Powershell New-Item call prepending path information when creating a symbolic link [duplicate]

I've written a script to create a series of symbolic links. I want to set the target value to $shortpath where
$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"
The value of the $shortpath variable is valid and I can open it from the run command. The string that PS is trying to write at the creation of the symlink is different than anticipated. I expect that it would write the value of the string, or at least insert the value of the Env Variable. rather it is adding to the string I pass to it.
New-Item -Path $currpath -ItemType SymbolicLink -Value ($targetpath) -Force
I would expect a target value to be: c:\Users\UserName\dir1\dir2\dir3\filename.ext or %userprofile%\dir1\dir2\dir3\filename.ext
Instead, I am getting: C:\windows\system32%UserProfile$\dir1\dir2\dir3\filename.ext
example of output written to logfile:
wholepath = C:\Users\UserName\dir1\dir2\dir3\longfilename1.ext
spath = C:\Users\UserName\dir1\dir2\dir3\longfi~1.ext
envpath = C:\Users\UserName\
midpart = dir1\dir2\dir3\
filename = longfi~1.ext
targetpath = %UserProfile%\dir1\dir2\dir3\longfi~1.ext
Could anyone shed some light as to why this may be happening? The same thing is happening if i user mklink. I've added the entire script below:
function Get-ShortPathName
{
Param([string] $path)
$MethodDefinition = #'
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError = true)]
public static extern int GetShortPathName(string pathName, System.Text.StringBuilder shortName, int cbShortName);
'#
$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru
$shortPath = New-Object System.Text.StringBuilder(500)
$retVal = $Kernel32::GetShortPathName($path, $shortPath, $shortPath.Capacity)
return $shortPath.ToString()
}
$logfile="C:\SwSetup\SymLinkScript\log.txt"
<#paths to orignials and place to copy to#>
$src = $env:userprofile + "\Firestone Technical Resources, Inc\Firestone Technical Resources, Inc Team Site - Documents\Danielle"
$dest = "C:\SwSetup\asdfg\"
$src = $src.Replace("\","\\")
<# grab the root object, and its children, from the src#>
$i = Get-ChildItem -LiteralPath $src -Recurse
<# recurse through the root and process, for lack of a better term, each object#>
$i | ForEach-Object {
Process {
$apath = $_.FullName -Replace $src,""
$cpath = $dest + $apath
<# Create Directory if it does not exist#>
If(!(Test-Path (Split-Path -Parent $cpath))){
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $cpath)
}
<#
Create the SymLink if it does not exist
mklink syntax | PowerShell equivalent
mklink /D Link Target | new-item -path <path to location> -itemtype symboliclink -name <the name> -value <path to target>
#>
If(!$_.PSIsContainer){
If(!(Get-Item $cpath -ErrorAction SilentlyContinue)){
<#establish 8.3path#>
$wholepath = ([WildcardPattern]::Escape($_.FullName))
$shortPath = Get-ShortPathName($wholepath)
$envpath = $shortpath.substring(0,18)
$midpart = ((Split-path $shortpath -parent).trimstart($envpath)) +"\"
$filename = Split-Path $shortpath -leaf
$targetpath = "%UserProfile%\" + $midpart + $filename
<#write to log file#>
"wholepath = " + $wholepath >> $logfile
"spath = " + $Shortpath >>$logfile
"envpath = " + $envpath >> $logfile
"midpart = " +$midpart >>$logfile
"filename = " + $filename >> $logfile
"targetpath = " + $targetpath >> $logfile
"cpath = " + [string]$cpath >> $logfile
"----------" >>$logfile
" " >>$logfile
<#create symlink#>
New-Item -Path $cpath -ItemType SymbolicLink -Value ($targetpath) -Force
<#cmd /c mklink $cpath $targetpath#>
<#create shortcut
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($targetpath.substring(0,$targetpath.Length-4) + '.lnk')
$Shortcut.TargetPath = $targetpath
$Shortcut.Save()#>
}
}
}
}
Shortcut files (.lnk) do support cmd.exe-style environment variable references (e.g. %USERPROFILE%) in their properties, but there's a caveat:
When a shortcut file is created or modified with WshShell COM object's .CreateShortcut() API:
Assigning property values, say, .TargetPath = '%USERPROFILE%\Desktop' works as expected - that is, the string is stored as-is and the reference to environment variable %USERPROFILE% is only expanded at runtime, i.e. when the shortcut file is opened.
However, querying such properties via this COM API also performs expansion, so that you won't be able to get raw definitions, and won't be able to distinguish between the .TargetPath property containing %USERPROFILE%\Desktop and, say, verbatim C:\Users\jdoe\Desktop
When in doubt about a given .lnk file's actual property values, inspect it via File Explorer.
Symbolic links (symlinks) do not.
The target of a symbolic link must be specified as a literal path, which in your case means using PowerShell's environment-variable syntax (e.g., $env:UserProfile) in order to resolve the variable reference to its value up front:
# This results in a *literal* path, because $env:UserProfile is
# instantly resolved; the result can be used with New-Item / mklink
$literalTargetPath = "$env:UserProfile\" + $midpart + $filename
Passing something like '%USERPROFILE%\Desktop' to the -Value / -Target parameter of New-Item -Type SymbolicLink therefore does not work (as intended), but the symptom depends on which PowerShell edition you're running:
Windows PowerShell, which you're using, tries to verify the existence of the target path up front, and therefore fails to create the symlink, because it interprets %USERPROFILE%\Desktop verbatim, as a relative path (relative to the current directory, which is why the latter's path is prepended), which doesn't exist.
PowerShell (Core) 7+ also interprets the path verbatim, but it allows creating symlinks with not-yet-existing targets, so the symlink creation itself succeeds. However, trying to use the resulting symlink then fails, because its target doesn't exist.
First you have set a variable called $shortpath:
$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"
and then you say:
New-Item -Path $currpath -ItemType SymbolicLink -Value ($targetpath)
-Force
I would expect a target value to be:
c:\Users\UserName\dir1\dir2\dir3\filename.ext or
%userprofile%\dir1\dir2\dir3\filename.ext
The reason your expectation is not met is that your New-Item line doesn't refer to your $shortpath variable.

Powershell 5.1.17763.1007 How to execute a string-command stored in a variable (Copy-Item)

I have a simple tasks that doesnt work:
$Copy = copy-item -path "C:\Folder 0" -destination "C:\Folder $x\" -recurse
for($x=1; $x -le 9;$x++)
{
$Copy
}
I cannot execute the command in the variable $Copy, when I run the loop it just prints $Copy to the console as the variable seems to be empty.
I tried Invoke-Expression, & $Copy, putting the Copy-Item command under "", but that doesnt work here...
Any advice for a beginner?
Thanks in advance!
As explained in the comments, Copy-Item doesn't return anything by default, so the value of $copy is $null.
From your clarifying comment:
I wanted actually just to store the command
If you want an executable block of code that you can invoke later, you might want to define a [scriptblock]. Scriptblock literals in PowerShell are easy, simply wrap your code in {}, and optionally supply parameter definitions:
$CopyCommand = {
param([string]$X)
Copy-Item -Path "C:\Folder 0" -Destination "C:\Folder $X\" -Recurse
}
& $CopyCommand 1
# later in the script
& $CopyCommand 2
or you can define a function with the same:
function Copy-MyFolderTree
{
param([string]$X)
Copy-Item -Path "C:\Folder 0" -Destination "C:\Folder $X\" -Recurse
}
Copy-MyFolderTree -X 1
# later in the script
Copy-MyFolderTree -X 2

Powershell Join-Path showing 2 dirs in result instead of 1 - accidental script/function output

I am constructing incremental directory structures, and for some reason Join-Path is showing 2 dirs. When I later join that with a file I'm sending to copy-item, it causes an error, as shown below. I have shown in the comment for the $to_loc_finalDT1 line, where I first see these two dirs:
Copy-Item : Cannot find path '\\T2\DisasterBackup\Loc_2019-03-08\Privileges\Privileges_HH_Bak.csv \\T2\DisasterBackup\Loc_2019-03-08\Privileges\Privileges_HH_Bak.csv' because it does not exist
So this is the pertinent powershell script:
$T2 = "\\T2\DisasterBackup\Loc"
$toLocParentDT2 = CreateDatedFolder $parentDirBaseNameDT2
$to_loc_finalDT2 = Join-Path -Path $toLocParentDT2 -ChildPath "Privileges"
#create sub-folder location
if(-Not (Test-Path $to_loc_finalDT2 ))
{
write-output " Creating folder $to_loc_finalDT2 because it does not exist "
New-Item -ItemType directory -Path $to_loc_finalDT2 -force
}
#second dir save files to
$parentDirBaseNameDT1 = "\\T1\DisasterBackup\Loc"
$toLocParentDT1 = CreateDatedFolder $parentDirBaseNameDT1
$to_loc_finalDT1 = Join-Path -Path $toLocParentDT1 -ChildPath "Privileges" #shows 2 dirs here in debugger: \\T2\DisasterBackup\Loc_2019-03-08\Privileges \\T2\DisasterBackup\Loc_2019-03-08\Privileges
#create sub-folder location
if(-Not (Test-Path $to_loc_finalDT1 ))
{
write-output " Creating folder $to_loc_finalDT1 because it does not exist "
New-Item -ItemType directory -Path $to_loc_finalDT1 -force
}
I'm not sure how to get Join-Path to just have the one dir, as it should. Right now, I think it's being treated as an array, which is not correct.
I tried searching for related issues, but didn't see anything similar.
Update
Here's the code for CreateDatedFolder:
#create dated folder to put backup files in
function CreateDatedFolder([string]$name){
$datedDir = ""
$datedDir = "$name" + "_" + "$((Get-Date).ToString('yyyy-MM-dd'))"
New-Item -ItemType Directory -Path $datedDir -force
return $datedDir
}
The output for that looks fine when it's returned. It appends the date onto the \T2\DisasterBackup\Loc, but the debugger only shows one dir there, not an array or 2 dirs that are separate strings.
As T-Me correctly inferred before you posted the CreateDatedFolder source, the problem is that the function inadvertently outputs 2 objects, and Join-Path accepts an array of parent paths to each join with the child path.
Specifically, it is the New-Item call that accidentally creates an additional output object, just before your return $datedDir call.
New-Item outputs a [System.IO.DirectoryInfo] instance representing the newly created directory and, due to PowerShell's implicit output behavior, that instance becomes part of what the function outputs too - any command or expression inside a script / function that returns a value that isn't captured or redirected becomes part of the output.
To prevent that, suppress the output:
$null = New-Item -ItemType Directory -Path $datedDir -force
Other ways to suppress output are discussed in this answer, which also discusses the design rationale for PowerShell's implicit output behavior.
Note that you never need return in PowerShell in order to output a result - but you may need it for flow control, to exit a function prematurely:
return $datedDir
is syntactic sugar for:
$datedDir # Implicitly output the value of $datedDir.
# While you could also use `Write-Output $datedDir`,
# that is rarely needed and actually slows things down.
return # return from the function - flow control only
For more information about PowerShell's implicit output behavior, see this answer.

PowerShell and Robocopy - filename incorrect when trying to pass variable folder as destination

I'm trying to use a PowerShell script running Robocopy* to back some files up to a newly-made directory:
$Timestamp = Get-Date -format ddMMyyyy
$DestFolder = "`"\\NASBOX\Archives\$Timestamp\`""
$SourceFolder = "`"\\DESKTOP\d$`""
ROBOCOPY $SourceFolder $DestFolder /COPYALL /B /R:10 /W:90 /LOG:$Timestamp.txt /FP /TEE
This gives me the following error:
2018/01/23 16:26:20 ERROR 123 (0x0000007B) Accessing Destination Directory \\NASBOX\Archives\23012018" \COPYALL \B \R:10 \W:90 \LOG:23012018.txt \FP \TEE\
The filename, directory name, or volume label syntax is incorrect.
I've tried a few different methods, including passing the arguments as an array. Every single thing I've tried results in the exact same error.
I roughly understand why this is happening, but despite ~two hours spent online I can't find a solution that works in my specific context.
Where am I going wrong?
* I tried using Copy-Item but there are some super long directory paths on this desktop's "D" drive.
The issue is the trailing slash in the path you are building:
"\\NASBOX\Archives\23012018\"
This slash is escaping the double quote for robocopy, it is seeing this path as including a quote symbol at the end :
\\NASBOX\Archives\23012018"
The error message shows this, but isn't very helpful! To fix the issue, simply remove the trailing slash from your path:
$DestFolder = "`"\\NASBOX\Archives\$Timestamp`""
You don't need to try so hard with escaping of quotes in your variables. PowerShell handles most of this for you. This should be all you need to do:
$Timestamp = Get-Date -Format ddMMyyyy
$SourceFolder = "\\DESKTOP\d$"
$DestFolder = "\\NASBOX\Archives\$Timestamp"
ROBOCOPY $SourceFolder $DestFolder /COPYALL /B /R:10 /W:90 /LOG:$Timestamp.txt /FP /TEE
Note that the destination folder shouldn't include a trailing \.
TL;DR - It is not necessary to create strings with embedded " characters to pass to robocopy. Just put the variables on the robocopy command line and PowerShell will quote automatically when necessary.
Function Copy-File {
[CmdletBinding()]
Param(
[Parameter(Position=0)]
[string]$source,
[Parameter(Position=1)]
[string]$dest,
[Parameter(Position=2)]
[string]$sourcefile,
[Parameter(Position=3)]
[ref]$RoboError
)
Write-Log -message "Copying $sourcefile from $source to $dest"
$robotoday=(Get-Date).ToString('yyyyMMdd')
$logfile = -join($env:systemdrive, '\logs\', $robotoday, '_robocopy.log')
$what = #("$sourcefile",'/COPY:DAT', '/Z', '/E')
$options = #("/R:1","/W:1","/TEE","/ETA","/LOG+:$logfile")
$cmdArgs = #($source,$dest,$what,$options)
robocopy #cmdArgs
if ($lastexitcode -gt 7) {
$RoboError.value=$TRUE
Write-Log -level 'warn' -message "Robocopy function failed with error: $lastexitcode"
}
} # End Copy-File
[bool]$RoboError=$FALSE
Copy-File -source $copysource -dest $copydestination -sourcefile '*' -RoboError([ref]$RoboError)

How do I use Join-Path to combine more than two strings into a file path?

If I want to combine two strings into a file path, I use Join-Path like this:
$path = Join-Path C: "Program Files"
Write-Host $path
That prints "C:\Program Files". If I want to do this for more than two strings though:
$path = Join-Path C: "Program Files" "Microsoft Office"
Write-Host $path
PowerShell throws an error:
Join-Path : A positional parameter cannot be found that accepts argument 'Microsoft Office'.
At D:\users\ma\my_script.ps1:1 char:18
+ $path = join-path <<<< C: "Program Files" "Microsoft Office"
+ CategoryInfo : InvalidArgument: (:) [Join-Path], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell
.Commands.JoinPathCommand
I tried using a string array:
[string[]] $pieces = "C:", "Program Files", "Microsoft Office"
$path = Join-Path $pieces
Write-Host $path
But PowerShell prompts me to enter the childpath (since I didn't specify the -childpath argument), e.g. "somepath", and then creates three files paths,
C:\somepath
Program Files\somepath
Microsoft Office\somepath
which isn't right either.
You can use the .NET Path class:
[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Since Join-Path can be piped a path value, you can pipe multiple Join-Path statements together:
Join-Path "C:" -ChildPath "Windows" | Join-Path -ChildPath "system32" | Join-Path -ChildPath "drivers"
It's not as terse as you would probably like it to be, but it's fully PowerShell and is relatively easy to read.
Since PowerShell 6.0, Join-Path has a new parameter called -AdditionalChildPath and can combine multiple parts of a path out-of-the-box. Either by providing the extra parameter or by just supplying a list of elements.
Example from the documentation:
Join-Path a b c d e f g
a\b\c\d\e\f\g
So in PowerShell 6.0 and above your variant
$path = Join-Path C: "Program Files" "Microsoft Office"
works as expected!
Join-Path is not exactly what you are looking for. It has multiple uses but not the one you are looking for. An example from Partying with Join-Path:
Join-Path C:\hello,d:\goodbye,e:\hola,f:\adios world
C:\hello\world
d:\goodbye\world
e:\hola\world
f:\adios\world
You see that it accepts an array of strings, and it concatenates the child string to each creating full paths. In your example, $path = join-path C: "Program Files" "Microsoft Office". You are getting the error since you are passing three positional arguments and join-path only accepts two. What you are looking for is a -join, and I could see this being a misunderstanding. Consider instead this with your example:
"C:","Program Files","Microsoft Office" -join "\"
-Join takes the array of items and concatenates them with \ into a single string.
C:\Program Files\Microsoft Office
Minor attempt at a salvage
Yes, I will agree that this answer is better, but mine could still work. Comments suggest there could be an issue with slashes, so to keep with my concatenation approach you could do this as well.
"C:","\\Program Files\","Microsoft Office\" -join "\" -replace "(?!^\\)\\{2,}","\"
So if there are issues with extra slashes it could be handled as long as they are not in the beginning of the string (allows UNC paths). [io.path]::combine('c:\', 'foo', '\bar\') would not work as expected and mine would account for that. Both require proper strings for input as you cannot account for all scenarios. Consider both approaches, but, yes, the other higher-rated answer is more terse, and I didn't even know it existed.
Also, would like to point out, my answer explains how what the OP doing was wrong on top of providing a suggestion to address the core problem.
If you are still using .NET 2.0, then [IO.Path]::Combine won't have the params string[] overload which you need to join more than two parts, and you'll see the error Cannot find an overload for "Combine" and the argument count: "3".
Slightly less elegant, but a pure PowerShell solution is to manually aggregate path parts:
Join-Path C: (Join-Path "Program Files" "Microsoft Office")
or
Join-Path (Join-Path C: "Program Files") "Microsoft Office"
Here are two more ways to write a pure PowerShell function to join an arbitrary number of components into a path.
This first function uses a single array to store all of the components and then a foreach loop to combine them:
function Join-Paths {
Param(
[Parameter(mandatory)]
[String[]]
$Paths
)
$output = $Paths[0]
foreach($path in $Paths[1..$Paths.Count]) {
$output = Join-Path $output -ChildPath $path
}
$output
}
Because the path components are elements in an array and all part of a single argument, they must be separated by commas. Usage is as follows:
PS C:\> Join-Paths 'C:', 'Program Files', 'Microsoft Office'
C:\Program Files\Microsoft Office
A more minimalist way to write this function is to use the built-in $args variable, and then collapse the foreach loop into a single line using Mike Fair's method.
function Join-Paths2 {
$path = $args[0]
$args[1..$args.Count] | %{ $path = Join-Path $path $_ }
$path
}
Unlike the previous version of the function, each path component is a separate argument, so only a space is necessary to separate the arguments:
PS C:\> Join-Paths2 'C:' 'Program Files' 'Microsoft Office'
C:\Program Files\Microsoft Office
Here's something that will do what you'd want when using a string array for the ChildPath.
$path = "C:"
#( "Program Files", "Microsoft Office" ) | %{ $path = Join-Path $path $_ }
Write-Host $path
Which outputs
C:\Program Files\Microsoft Office
The only caveat I found is that the initial value for $path must have a value (cannot be null or empty).
The following approach is more concise than piping Join-Path statements:
$p = "a"; "b", "c", "d" | ForEach-Object -Process { $p = Join-Path $p $_ }
$p then holds the concatenated path 'a\b\c\d'.
(I just noticed that this is the exact same approach as Mike Fair's, sorry.)
You can use it this way:
$root = 'C:'
$folder1 = 'Program Files (x86)'
$folder2 = 'Microsoft.NET'
if (-Not(Test-Path $(Join-Path $root -ChildPath $folder1 | Join-Path -ChildPath $folder2)))
{
"Folder does not exist"
}
else
{
"Folder exist"
}
Or you could write your own function for it (which is what I ended up doing).
function Join-Path-Recursively($PathParts) {
$NumberOfPathParts = $PathParts.Length;
if ($NumberOfPathParts -eq 0) {
return $null
} elseif ($NumberOfPathParts -eq 1) {
return $PathParts[0]
} else {
return Join-Path -Path $PathParts[0] -ChildPath $(Join-Path-Recursively -PathParts $PathParts[1..($NumberOfPathParts-1)])
}
}
You could then call the function like this:
Join-Path-Recursively -PathParts #("C:", "Program Files", "Microsoft Office")
Join-Path-Recursively #("C:", "Program Files", "Microsoft Office")
This has the advantage of having the exact same behaviour as the normal Join-Path function and not depending on the .NET Framework.