I've a build system which uses robocopy to copy files from one system to our server, and to a specific path. The following has worked well till a new requirement was introduced:
robocopy local\dist \\server01\somepath\dist XF *.* /E
Now, we want to have a changing 'dist' name to include build information. For example, 'dist1', 'dist2', 'distabcd'. Anyhow, the point is, that the folder name is changing. How do I tell robocopy to match on any name beginning with 'dist', but copy to the correct full named dist folder on the remote server
robocopy local\dist* \\server01\somepath\[????] XF *.* /E
I have the option to use PowerShell commands to do this, assuming it may be able to copy to the server location. I know almost nothing about PowerShell, but welcome any tips.
Powershell provides RegEx functionality with the '-match' and '-contains' operators. Here would be an example of what capturing changing directories would look like:
$localDirectory = "local\dist"
$directory = "\\server01\somepath\dist"
$keyword = "dist"
$fileDirectory = Get-ChildItem -Path $directory -Recurse
foreach ($container in $fileDirectory)
{
# -match is one of the RegEx functions we may utilize for this operation
# e.g dist1.. dist2.. distabc.. adist.. mynewdistrubition
if ($container -match $keyword)
{
try
{
Copy-Item -Path "$($directory)\$($container)" -Destination $localDirectory -Force -Recurse
}
catch [System.Exception]
{
Write-Output $_.Exception
}
}
}
Related
With PowerShell 5.1 and the new .NET framework, it is finally possible to work with long path names using Get-ChildItem -Path "\\?\C:\MyPath".
In the code below we try to filter out files and folders that are in specific directories (as well as the directories themselves), because we're not interested in them.
This code works fine, but we were wondering if there is a better or faster way of doing this instead of building a long regex string?
In case the array $IgnoredFolders gets really long, it might take more time for -notmatch to make the decision. I'm not an expert in regex and how long they may become, so any feedback is welcome.
<#
# Path
Folder A
File folder A.txt
Folder B
File folder B.txt
Folder C
File folder C.txt
File root A.txt
File root B.txt
#>
$Path = 'S:\testFolder'
$IgnoredFolders = #(
"$Path\Folder B"
"$Path\Folder C"
)
$RegexIgnoredFolders = $IgnoredFolders.ForEach({
[Regex]::Escape("\\?\$_")
}) -join '|'
#(Get-ChildItem -LiteralPath "\\?\$Path" -Recurse).Where({
$_.FullName -notmatch $RegexIgnoredFolders
}) | select fullname
The output of this code is:
"$Path\Folder A"
"$Path\File root A.txt"
"$Path\File root B.txt"
"$Path\Folder A\File folder A.txt"
I currently have a powershell script that I move files to specific folders based on the file names. The top of the script starts with setting a variables for the destination path where a certain group of files should go:
$FileName = "path to where files with that name go"
Then I read in the contents of the entire directory of files recursively into a variable:
$Files = Get-ChildItem $FileFolder -File -Recurse
Then I have a bunch of lines of the same command for matching and moving:
$Files | Where-Object { $_.Name -match 'some name' } | Move-Item -Destination "$Variable-set-above" -Force
It was fine when it was 10 or 20 matches, but with more and more files being added and needing to be organized, I want to see if I can clean up the script by having it build the destination folder structure based on the file name instead of having a line for every match case, and a line for every move.
I was looking into Split-Path, regex -split, String.split(), and some other options, and I think I'm close, but I can't find an example anywhere of where someone takes the first portion of the file name, up to a certain couple of characters, keeping the first part, and excluding the rest. Kind of like a Split-Ignoresecond or something like that.
I'm testing doing this first before modifying my main script, I have this so far:
3 files in a folder named Test.One.File.D0001.txt, Test.Two.File.D0001.txt, and Test.Three.File.D0001.txt.
My test script:
$Testfiles = Get-ChildItem -Name *.txt
$Testfiles.replace('.',' ') -split "D0"
Which gives me an output of:
Test One File
001 txt
Test Three File
001 txt
Test Two File
001 txt
It's weird that it's not in the right order, but I envision that I'd be just dealing with 1 file at a time anyway so that won't matter.
What I'd like to do is read in a file name, ignore the "001 txt" part, use the first part of the filename to build the last part of a destination path for the file move, and then move the file to that destination. I could use Split-Path -Leafbase but I can't figure out the syntax for it to not give me an error, and I'd still be left with part of the filename I don't want.
Say I have a file called One.Two.ThreeD0001 that needs to go to D:\Files\Onestwosthrees. I want my script to read in the files from a folder, and then process the file One.Two.ThreeD0001.txt so that all that's left is "One Two Three", stick it in a variable like $SplitFile, then move the file to a folder built from the filename like D:\Files\Onestwosthrees\$SplitFile.
There's further parsing I want to do, but if I can get this part down I can figure out the sub parsing I need.
Some sources I've looked at so far for clues are:
https://superuser.com/questions/817955
and
https://kevinmarquette.github.io/2017-07-31-Powershell-regex-regular-expression/
Think you were pretty much there:
cd "C:\Users\users\Downloads\StackTesting"
$testFiles = Get-Childitem -Include *.txt
foreach ( $item in $testfiles ) {
$directory = ($item.name.replace('.', '') -split "D0")[0]
## check if folder exists, if not create
if (!(Test-Path "C:\Users\user\Downloads\StackTesting\$directory"))
{
New-Item -Type Directory "C:\Users\users\Downloads\StackTesting\$directory"
}
ELSE
{
Write-Host "Folder exists"
}
## Move item to folder
Move-item $item.fullname -Destination "C:\Users\users\Downloads\StackTesting\$directory"
}
This is how i got your directory names:
$directory = ($item.name.replace('.', '') -split "D0")[0]
Changed from a space to no space as your examples at the bottom didn't have spaces.
I need to be able to parse the folder path (which could vary) from an installed Windows service.
In PowerShell using
$serviceToRemove = Get-WmiObject -Class Win32_Service -Filter "name='Labelary'"
I can get the object, and if I do
$serviceToRemove.PathName
it gives
c:\program files\myapplicationname\mybinary.exe //rs/labelry
I need to get
c:\program files\myapplicationname
I've tried various combinations of splitting, but the path could vary or have spaces in it.
Is there a regex way of getting this portion of the path. The only guaranteed pattern is that I want the portion of the string which is before the wildcard *.exe . But I don't know how to express this in PowerShell. I tried \w*\.exe in a regex tester but this just returns the mybinary.exe.
Also various combinations of PowerShell or System.IO path tools seem to consider the //rs/labelry to be part of the path and thus not return the root correctly.
I would probably write it this way:
$pathName = Get-WmiObject Win32_Service -Filter "Name='Labelary'" |
Select-Object -ExpandProperty PathName |
Select-String '^"?(.+)\.exe' | ForEach-Object {
Split-Path $_.Matches[0].Groups[1].Value -Parent
}
Something like this should work:
$path = if ($serviceToRemove.PathName -match '[a-z]:\\.*?(?=\\[^\\]+\.exe)') {
$matches[0]
}
The regular expression matches a drive letter ([a-z]:\\) plus the shortest sequence of characters before a backslash followed by an executable name (\\[^\\]+\.exe). The positive lookahead assertion ((?=...)) ensures that the backslash and executable name are not included in the returned string.
I would like to do all 8k+ files at once using the pattern since the left 5 chars of the file matches the right 5 of the sub-directory to move to.
This works one by one:
move-item -path X:\"Property Files"\05165*.pdf -destination X:\"Property Files"\"* -- 05165";
move-item -path X:\"Property Files"\05164*.pdf -destination X:\"Property Files"\"* -- 05164";
Thank you in advance for any help.
As a one-liner, assuming destination folder already exist:
Get-ChildItem "X:\Property Files\*.PDF" |
ForEach { move -path $_ -destination ($_.directoryname +"\* -- "+ $_.Name.substring(0,5))}
Using only the filename, you just extract the first five chars (substring(0,5)), then use it as the end of the folder to match.
$_.Directoryname assume destination folder is a subfolder of the source path.
Ok, you're on the right path with the whole RegEx tag here. What I did is look for everything until the last backslash, then captured 5 digits, then everything that isn't a backslash until the end of the line, and only returned the captured group. I set that as a variable $ItemNumber, and used that in the destination. I ran that on a ForEach loop for everything in the target source folder. Here's the code I ended up with:
ForEach($File in (GCI "X:\Property Files\*.PDF")){
$ItemNumber = $File.Fullname -replace ".+?\\(\d{5})[^\\]*$", "`$1"
move-item -path X:\"Property Files"\05165*.pdf -destination X:\"Property Files"\"* -- $ItemNumber"
}
You could do it through the pipe if you wanted, like this:
GCI "X:\Property Files\*.PDF"|%{move-item -path X:\"Property Files"\05165*.pdf -destination X:\"Property Files"\"* -- $($_.Fullname -replace ".+?\\(\d{5})[^\\]*$", "`$1")"}
But that gets kind of long, and some people really don't like that long of a single line.
That RegEx can be tested here, plus it breaks it all down. (link to regex101.com explanation)
In a deployment szenario, I need to rename config files. There are config files for every environment (Dev.Test, Dev.Prod, Integration, Prod). For example a web.config would be called web.Dev.Test.config if it was for the Dev.Test environment. On the target machine, I need to rename the files back to their original name (i.e. from web.Dev.Test.config to web.config) with Powershell.
$test = "web.Dev.Prod.config"
$environment = $test | Select-String -Pattern ".*\.(?<environment>(Dev.Test|Dev.Prod|Prod|Integration))\.config" | select -expand Matches | foreach {$_.groups["environment"].value}
if ($test -match "Dev.Prod")
{
$environment = "Dev.Prod"
}
$environment
$newFileName = $test.Remove($test.IndexOf($environment),$environment.Length + 1)
$newFileName
The problem I have with this is, that the Regex does not find the Dev.Prod evironment, but returns Prod instead. This is why I introduced the if statement. I was wondering if there was a more elegant way of renaming the files with Powershell.
Watch out for greedy matching. Modify your regex that starts ".*\.(?" to ".*?\.(?".