Powershell Variable for dynamic Move-Item - powershell

I have a function that looks at some registry settings for SQL Server which just pulls put the data and log location and puts them in variables. But I run into an issue when I pass them into Move-Item. Basically:
fnGetDataNLog
Returns $datalocation, $loglocation
When I run Move-Item -Path $datalocaton -Destination $loglocation I get
Cannot bind argument to parameter 'path' because it does not exist.
Is that due to its passing a runtime variable? Is there another way to do that then.

You can run a Test-Path to ensure the location exists.
If you $Datalocation is a UNC path to a directory you can do the below. eg. $Datalocation = '\\UNC\Path\To\Folder'
If(-not (Test-Path -Path $Datalocation)) {
New-Item -Path $Datalocation -ItemType Directory
}
Move-Item -Path $Datalocation-Destination $loglocation
This will test if the path at $Datalocation exists, if -not then it will create it.
if the UNC path is a string that goes to a file, ie. not an object result from a Get-ChildItem, then you can use a bit of regex to get the parent folder and then do the below. The regex will remove everything after the last \.
eg. \\UNC\Path\To\Folder\File.txt becomes \\UNC\Path\To\Folder\
If(-not (Test-Path -Path ($Datalocation -replace '[^\\]+$'))) {
New-Item -Path $Datalocation -ItemType Directory
}
Move-Item -Path $Datalocation -Destination $loglocation

Related

PowerShell search recursively for files and folder which the name contains "..."

I need help to create a script that can save my life.
My backup software got it wrong because I misdesigned my backup plan and I have lots of named files; filename or folder name (2) (encoding conflict).
I would like to recursively search my network share to find folders and files with "(encode conflict)" in their name and first export them for verification.
Then, if all goes well, I would like to move them to another place while keeping the folder's hierarchy and that's where I stuck.
Get-ChildItem -LiteralPath '\\?\E:\Network Shares\Commun' -Recurse -Filter "*(encode*" #| move-item -Destination 'C:\Users\Desktop\Conflits\'
For the export I found the script here :
https://stackoverflow.com/a/15261816/19493679
Thanks to OP
The files from my test are moving without folder's and file's hierarchy ...
Can you help me please ? :)
Move-Item doesn't know about the directory structure, it just gets paths fed one by one from Get-ChildItem. For preserving directory structure, Move-Item would have to know a common base path, but there is currently no way to specify one.
So I've created a helper function New-DestinationPath that can be chained in between Get-ChildItem and Move-Item (or Copy-Item). The function creates the destination directory structure and outputs the source path and the fully resolved destination path.
Function New-DestinationPath {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('Fullname', 'PSPath')]
[String] $Path,
[Parameter(Mandatory, Position=0)]
[String] $CommonPath,
[Parameter(Mandatory, Position=1)]
[String] $Destination
)
process {
# Temporarily change current directory -> base directory for Resolve-Path -Relative
Push-Location $CommonPath
try {
# Resolve input path relative to $CommonPath (current directory)
$relativePath = Resolve-Path $Path -Relative
}
finally {
Pop-Location # Restore current directory
}
# 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).
$null = New-Item $targetDir -ItemType Directory -Force
# Output the full source and destination paths (for use with Copy-Item or Move-Item)
[PSCustomObject]#{
Path = $Path
Destination = $targetPath
}
}
}
Usage:
$searchPath = '\\?\E:\Network Shares\Commun'
Get-ChildItem -LiteralPath $searchPath -Recurse -File |
Where-Object Fullname -like '*(encode*' |
New-DestinationPath -CommonPath $searchPath -Destination 'C:\Users\Desktop\Conflits' -WhatIf |
Move-Item -LiteralPath { $_.Path } -Destination { $_.Destination } -WhatIf
By using the common parameter -WhatIf the functions only output what they would do. Remove both -WhatIf arguments when you have confirmed that the right paths are used.
In the Move-Item call we are using delay-bind scriptblocks to avoid a ForEach-Object command. Using the automatic variable $_ we can directly refer to the output of the previous command in the pipeline (New-DestinationPath).

CSV - Piping to Copy-Item

When I try to import a CSV, and take a source filename/path and destination folder ref, copy-item seems to not copy the file in question.
I have a folder full of files in C:\Dir1\Test\Files\ and I need to copy them to individual folders in C:\Dir1\Test, based on what is in the csv.
$SourceDir = 'C:\Dir1\Test\Files\'
$DestDir = 'C:\Dir1\Test\'
Import-Csv C:\Dir1\Test\FileList.csv | ForEach-Object {
$Source = $SourceDir + $($_.'FilePath')
$Dest = $DestDir + "$($_.'Folder Ref')\"
Copy-Item $Source -Destination $Dest
}
If I switch out the Copy-Item to Write-Host, it reads to me correctly, am I doing something wrong?
Nothing happens, it returns me to the prompt with no output
Constructing file paths using string concatenation as you are doing is never a good idea..
Better use PowerShells cmdlet Join-Path for that or .Net [System.IO.Path]::Combine() method.
As mklement0 already commented, Copy-Item by default does not procude any visual output unless you add -Verbose.
You can also append switch -PassThru and in that case, the cmdlet returns an object that represents the copied item.
In your case, why not add an informative message yourself, something like:
$SourceDir = 'C:\Dir1\Test\Files'
$DestDir = 'C:\Dir1\Test'
Import-Csv -Path 'C:\Dir1\Test\FileList.csv' | ForEach-Object {
# construct the source path
$Source = Join-Path -Path $SourceDir -ChildPath $_.FilePath
if (Test-Path -Path $source -PathType Leaf) {
# construct the destination path
$Dest = Join-Path -Path $DestDir -ChildPath $_.'Folder Ref'
# make sure the target path exists before trying to copy to it
$null = New-Item -Path $Dest -ItemType Directory -Force
# now copy the file
Write-Host "Copying file '$Source' to '$Dest'"
Copy-Item -Path $Source -Destination $Dest
}
else {
Write-Warning "File '$Source' could not be found"
}
}

PowerShell script to copy jpg files from one folder to another by creating two subfolders with the same name

I am in need of some assistance, I am new to PowerShell and am trying to use it to make some of my work easier. I am writing a PowerShell script to copy JPG files from one location (C:\Pictures\People\People) and moving them to a new location.
The issue is that in this new location I need to create a folder with the same name as the JPG and then another subfolder with the same name again as the JPG.
So I need to move images from C:\Pictures\People\People which I will call JPG_Image to C:\Pictures\JPG_Name\JPG_Name\'JPG_Image'
So far I found and have been working with this:
$SourceFolder = "C:\Pictures\People\People"
$TargetFolder = "C:\Pictures\"
# Find all files matching *.JPG in the folder specified
Get-ChildItem -Path $SourceFolder -Filter *.jpg |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.jpg','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
# Create the directory if it doesn't already exits
if( -not ( Test-Path -Path $Destination.Directory.FullName ) ){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
You are making this harder on yourself than needs be.
Some enhancements to your code:
Add switch -File to the Get-ChildItem cmd so you do not also get DirectoryInfo objects
To get the filename without extension, there is a property .BaseName
Join-Path returns a string, no need to cast that into a [System.IO.FileInfo] object
If you add -Force to the New-Item cmd, there is no need to check if a folder already exists, because that will make the cmdlet either create a new folder or return the existing DirectoryInfo object.
Because we don't need that object (and the console output from it), we can just throw that away using $null = New-Item ...
Putting it all together:
$SourceFolder = "C:\Pictures\People\People"
$TargetFolder = "C:\Pictures"
# Find all files matching *.JPG in the folder specified
Get-ChildItem -Path $SourceFolder -Filter '*.jpg' -File |
ForEach-Object {
# Join-Path simply returns a string containing the combined path
# The BaseName property is the filename without extension
$ChildPath = Join-Path -Path $_.BaseName -ChildPath $_.BaseName
$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
# Create the directory if it doesn't already exits
# Using -Force will not give an error if the folder already exists
$null = New-Item -Path $Destination -ItemType Directory -Force
$_ | Copy-Item -Destination $Destination
}

Renaming a Folder and create a new folder in PowerShell

My folder structure: C:\example\latest.
I want to check if the subfolder latest already exists or not. If it does, I want to rename it as latest_MMddyyyy and then create a new folder called latest.
If it does not have latest already, then simple create the folder.
This is what I have:
param (
$localPath = "c:\example\latest\" #"
)
#Creating a new directory if does not exist
$newpath = $localPath+"_"+((Get-Date).AddDays(-1).ToString('MM-dd-yyyy'))
If (test-path $localPath){
Rename-Item -path $localpath -newName $newpath
}
New-Item -ItemType -type Directory -Force -Path $localPath
It is doing two things:
Rename my latest folder as _MM-dd-yyyy but I want it to rename as "latest_MM-dd-yyyy"
Throw an error: Missing an argument for parameter 'ItemType'. Specify a parameter of type 'System.String' and try again.
what am I doing wrong?
Throws an error: Missing an argument for parameter 'ItemType'. Specify a parameter of type 'System.String' and try again.
As Deadly-Bagel's helpful answer points out, you're missing an argument to -ItemType and instead follow it with another parameter, -Type, which, in fact, is an alias for -ItemType - so removing either -ItemType or -Type will work.
To find a parameter's aliases, use something like (Get-Command New-Item).Parameters['ItemType'].Aliases
Renames my latest folder to _MM-dd-yyyy, but I want latest_MM-dd-yyyy.
You append the date string directly to $localPath, which has a trailing \, so $newPath looks something like 'c:\example\latest\_02-08-2017', which is not the intent.
Ensuring that $localPath has no trailing \ fixes the problem, but do note that Rename-Item generally only accepts a file/directory name as a -NewName argument, not a full path; you can only get away with a full path if its parent path is the same as the input item's - in other words, you can only specify a path if it wouldn't result in a different location for the renamed item (you'd need the Move-Item cmdlet to achieve that).
Split-Path -Leaf $localPath offers a convenient way of extracting the last path component, whether or not the input path has a trailing \.
In this case: latest
Alternatively, $localPath -replace '\\$' would always return a path without a trailing \.
In this case: c:\example\latest
If we put it all together:
param (
$localPath = "c:\example\latest\" #"# generally, consider NOT using a trailing \
)
# Rename preexisting directory, if present.
if (Test-Path $localPath) {
# Determine the new name: the name of the input dir followed by "_" and a date string.
# Note the use of a single interpolated string ("...") with 2 embedded subexpressions,
# $(...)
$newName="$(Split-Path -Leaf $localPath)_$((Get-Date).AddDays(-1).ToString('MM-dd-yyyy'))"
Rename-Item -Path $localPath -newName $newName
}
# Recreate the directory ($null = ... suppresses the output).
$null = New-Item -ItemType Directory -Force -Path $localPath
Note that if you run this script more than once on the same day, you'll get an error on renaming (which could easily be handled).
try this
$localPath = "c:\temp\example\latest"
#remove last backslash
$localPath= [System.IO.Path]::GetDirectoryName("$localPath\") #"
#create new path name with timestamp
$newpath ="{0}_{1:MM-dd-yyyy}" -f $localPath, (Get-Date).AddDays(-1)
#rename old dir if exist and recreate localpath
Rename-Item -path $localpath -newName $newpath -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $localPath
New-Item -ItemType -type Directory -Force -Path $localPath
You're using -ItemType but not providing it a value, use this:
New-Item -ItemType Directory -Force -Path $localPath
To rename a folder, use the command: Rename-Item e.g
Rename-Item Old_Folder_Name New_Folder_Name

Test-Path Move-Item Problems

I run this PowerShell script, and it works fine on PowerShell 4.0. But I now have PowerShell 5.0 and the script does work but it throws an error:
The Script:
$path = "X"
$destination = "Y"
while (Test-Path -Path $path) {
Move-Item -Path "$path\*zip" -Destination "$destination"
}
The error I get is:
Move-Item : The process cannot access the file because it is being
used by another process.
The title of the question: "Test-Path Move-Item Problems" implies that one cmdlet might be impacting the other. That doesn't make sense to me as Test-Path is checking the folder's existence and Move-Item is working on child items within that folder.
Personally I would not use a while loop for this use case as, once you have determined that the path exists you don't need to keep testing it:
if(Test-Path -Path $path){
Move-Item -Path $path\*zip -Destination $destination
}
just do it
Move-Item -Path "$path\*zip" -Destination "$destination" -ErrorAction Ignore