Complex file rename - powershell

I have some 100 files in a folder. I want to rename those files. The format of files is like
AB1234.gif
B3245.gif
AB2541.gif
AB11422.jpg
and so on..
Output files shld be
AB-0111-1.gif
B-0111-2.gif
AB-0111-3.gif
AB-0111-4.jpg
Logic will be
for(int n=0;n<files.count;n++){
if(file is starting with AB)
fileName = AB + "-0111" + n;
if(file is starting with B)
fileName = B + "-0111" + n;
}
Is this thing possible with powershell script?

With the filename format that you describe, you can use the powershell -replace operator to replace the middle digits with the expression that you want. The only tricky part is that you have to maintain a counter as you loop through the items. It would look like this:
dir | foreach -begin{$cnt = 1} -process { mv $_ ($_.Name -replace '\d+',"-0111-$cnt"); $cnt += 1}

You can use [System.IO.Path]::GetExtension to get the extension of the file like this:
$ext = [System.IO.Path]::GetExtension('AB1234.gif')
and you can get the first alpha characters of the file name using the $matches variable like this:
if ('AB1234.gif' -match '^\D*') { $first = $matches[0]; }
and you can build your new file name like this:
$first + '-0111-' + '1' + $ext
You can replace the '1' above with a variable that you can increment by checking the exists property like this:
$i = 1;
while (test-path ($first + '-0111-' + $i + $ext)) {$i++}
when this loop completes, you will have the file name you need with $first + '-0111-' + $i + $ext and you can use this to rename the file with Rename-File:
Rename-File 'AB1234.gif' ($first + '-0111-' + $i + $ext)
now, wrap all that up in a loop and you should have it:
dir | ForEach-Object { ... }
for testing, add the -whatif parameter to the end of the Rename-File command and PS will tell you what it would do instead of actually performing the action.
thanks,
mark

Related

Reducing amout of lines in variable within loop in Powershell

I have a txt file containing 10000 lines. Each line is an ID.
Within every loop iteration I want to select 100 lines, put them in a special format and do something. I want to do this until the document is finished.
The txt looks like this:
406232C1331283
4062321N022075
4062321H316457
Current approach:
$liste = get-content "C:\x\input.txt"
foreach ($item in $liste) {
azcopy copy $source $target --include-pattern "*$item*" --recursive=true
}
The system will go throug the TXT file and make a copy request for every name it finds in the TXT file. Now the system is able to handle like 300 search-patterns in one request. like
azcopy copy $source $target --include-pattern "*id1*;*id2*;*id3*"
How can I extract 300 items from the document at once, separate them with semicolon and embedd them in wildcard? I tried to pipe everyting in a variable and work with -skip.
But it seems not easy to handle :(
Use the -ReadCount parameter to Get-Content to send multiple lines down the pipeline:
Get-Content "C:\x\input.txt" -ReadCount 300 | ForEach-Object {
$wildCards = ($_ | ForEach-Object { "*$_*" } -join ';'
azcopy copy $source $target --include-pattern $wildCards --recursive=true
}
Do you want 100 or 300 at a time? ;-)
I'm not sure if I really got what the endgoal is but to slice a given amount of elements in chunks of a certain size you can use a for loop like this:
$liste = Get-Content -Path 'C:\x\input.txt'
for ($i = 0; $i -lt $Liste.Count; $i += 100) {
$Liste[$i..$($i + 99)]
}
Now if I got it right you want to join these 100 elements and surround them with certain cahrachters ... this might work:
'"*' + ($Liste[$i..$($i + 99)] -join '*;*') + '*"'
Together it would be this:
$liste = Get-Content -Path 'C:\x\input.txt'
for ($i = 0; $i -lt $Liste.Count; $i += 100) {
'"*' + ($Liste[$i..$($i + 99)] -join '*;*') + '*"'
}
There's many ways, here's one of them...
First I would split array to chunks of 100 elements each, using this helper function:
Function Split-Array ($list, $count) {
$aggregateList = #()
$blocks = [Math]::Floor($list.Count / $count)
$leftOver = $list.Count % $count
for($i=0; $i -lt $blocks; $i++) {
$end = $count * ($i + 1) - 1
$aggregateList += #(,$list[$start..$end])
$start = $end + 1
}
if($leftOver -gt 0) {
$aggregateList += #(,$list[$start..($end+$leftOver)])
}
$aggregateList
}
For example to split your list into chunks of 100 do this:
$Splitted = Split-Array $liste -count 100
Then use foreach to iterate each chunk and join its elements for the pattern you need:
foreach ($chunk in $Splitted)
{
$Pattern = '"' + (($chunk | % {"*$_*"}) -join ";") + '"'
azcopy copy $source $target --include-pattern $Pattern --recursive=true
}

How to write to a csv file with no newline using powershell?

I want each row to be populated with the data retrieved from each file. Currently, the 2nd and 3rd column entries are being written to a newline.CSV file output I have tried using "export-csv" and the "-nonewline" command. Perhaps there is a regex command that would solve this?
#Column headings
$headings = "Source file, Review file existence, Review Result, Compilation Check Result, Static Analysis Result, Review up-to-date, Reviewed code version, Latest code version"
# Create array with $headings as first input
$output = #($headings)
$SourceParentDir = "C:\Document"
$Files = get-childitem -Path $SourceParentDir -Recurse -Filter '*.?pp' | % { $_.FullName }
foreach ($File in $Files)
{
$BaseName = [System.IO.Path]::GetFileName($File)
# Populate each row for each file
$output += $BaseName
$output += ", Review Exists" # writes to a newline
$output += ", " + $Result + "," + $Compilation + "," + $StaticAnalysis + "," + $UpToDateFlag + "," + $ReviewFileVersionNumber + "," + $SourceFileVersionNumber + ","
}
# write output to a csv file
$output | Out-File -FilePath Documents\Example-csv.csv -encoding utf8
You can do things that way, but there's definitely a more-Powershelley way:
Get-ChildItem -Path $SourceParentDir -Recurse -Filter '*.?pp' |
ForEach-Object {
$File = $_
# Put your other code here
# This will output an object to the stream
[PSCustomObject]#{
'Source file' = $File.Name
'Review file existence' = 'Review Exists'
'Review Result' = $Result
'Compilation Check Result' = $Compilation
'Static Analysis Result' = $StaticAnalysis
'Review up-to-date' = $UpToDateFlag
'Reviewed code version' = $ReviewFileVersionNumber
'Latest code version' = $SourceFileVersionNumber
}
} | Export-Csv Example-csv.csv -NoTypeInformation
The big drawback here is that you don't get a lot of formatting choices about the CSV. Every field is quoted, for example.
Alternately, if you really want really detailed control of the $output string, you should use a StringBuilder instead of a String. StringBuilder is one of the most potent and widely used classes in C#. This is because strings in C# and Powershell are immutable, so when you += a String you create a new string, copy everything over with the new bit, then throw the old string away. It can be very memory intensive with large operations. StringBuilder lets you get around all that. It's a class that's designed to let you append stuff to strings and format them however you want.
You instance it like so:
$output = [System.Text.StringBuilder]::new()
And then you typically call one of two methods to add text. Append($string) appends the string, AppendLine($string) appends the line and then adds a newline. You can also call AppendLine() with no argument to just add a newline. To get your final string, you call the ToString() method. The append methods do return a status when you call them which you can prevent from outputting pretty easily with a [void], or by saving it to another variable if you need it.
$output = [System.Text.StringBuilder]::new()
[void]$output.AppendLine($headings)
$SourceParentDir = "C:\StarTeam\00011114-JSENS_TRS\ATR\04_SW_Implementation\Operational"
$Files = get-childitem -Path $SourceParentDir -Recurse -Filter '*.?pp' | % { $_.FullName }
foreach ($File in $Files)
{
$BaseName = [System.IO.Path]::GetFileName($File)
# Populate each row for each file
[void]$output.Append($BaseName)
[void]$output.Append(", Review Exists")
[void]$output.Append(", $Result,$Compilation,$StaticAnalysis,$UpToDateFlag,$ReviewFileVersionNumber,$SourceFileVersionNumber,")
[void]$output.AppendLine()
}
$output.ToString() | Out-File -FilePath Documents\Example-csv.csv -encoding utf8
$output is an array, so each of those += inside the loop is a new entry in the array, and therefore a new line in the file.
You can fix this by using a temporary string variable in the middle of the loop, and appending it to $output once at the end of each iteration:
foreach ($File in $Files)
{
$row = [System.IO.Path]::GetFileName($File)
$row += ", Review Exists"
$row += ", " + $Result + "," + $Compilation + "," + $StaticAnalysis + "," + $UpToDateFlag + "," + $ReviewFileVersionNumber + "," + $SourceFileVersionNumber + ","
$output += $row
}
or by putting everything in one big string interpolation:
foreach ($File in $Files)
{
$BaseName = [System.IO.Path]::GetFileName($File)
$output += "$BaseName, Review Exists, $Result, $Compilation, $StaticAnalysis, $UpToDateFlag, $ReviewFileVersionNumber, $SourceFileVersionNumber"
}
But I agree with the other answer that building up an array of custom objects for the Export-Csv commandlet is more idiomatic for PowerShell.
The issue is how you're populating $Output. Since it is defined as an array, each time you're adding new information it's creating a new entry rather than just adding the additional information to a string.
If you make your $output into one line with all required fields it should correct it without you changing the array.
$output += $BaseName + ", Review Exists" + ", " + $Result + "," + $Compilation + "," + $StaticAnalysis + "," + $UpToDateFlag + "," + $ReviewFileVersionNumber + "," + $SourceFileVersionNumber + ","

List every sub folder in reverse

I have a UNC path
$path = "\\ad.testxyz.com\corp\technology\software\data\iso"
I need to apply permissions to the following paths:
\\ad.testxyz.com\corp\technology\software\data
\\ad.testxyz.com\corp\technology\software
\\ad.testxyz.com\corp\technology
And I don't want to do anything to \\ad.testxyz.com\corp\technology, or higher.
Here is my code so far. This "works", but I've run into a snag with directories that have spaces in them.
$path = "\\ad.domain.com\corp\technology\test\information\software"
#$path = "\\ad.domain.com\corp\technology\payer information\extracts\item load"
# Split string by "\", remove empty elements
$root = (($path.Split("\")).Split('',[System.StringSplitOptions]::RemoveEmptyEntries))
# Recursively list all top level folders in [path]. (Excludes DFS root)
for ($i=2; $i -lt $root.Length - 1; $i++) {
# Recursively build every directory up the tree
$leaf = "\" + $root[$i]
$branch += $leaf
$trunk = "\\" + $root[0] + "\" + $root[1] + $branch
# do work
Write-Host $trunk
}
Outputs:
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology\test\information
But if I try and use a path with spaces in it, I get the following
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\payer
\\ad.domain.com\corp\technology\payer\information
\\ad.domain.com\corp\technology\payer\information\extracts
\\ad.domain.com\corp\technology\payer\information\extracts\item
Figured it out. This works for anyone else.
$path = "\\ad.domain.com\corp\technology\test\payer information\software"
# Split string by "\", remove null elements
$tree = $path.Split([system.io.path]::DirectorySeparatorChar) | ? {$_}
# Recursively list all top level folders in [path]. (Excludes DFS root)
For ($i=2; $i -lt $tree.Length - 1; $i++) {
# Recursively build every directory up the tree
$leaf = "\" + $tree[$i]
$branch += $leaf
$trunk = "\\" + $tree[0] + "\" + $tree[1] + $branch
# do work
Write-Host $trunk
}
Outputs:
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology\test\payer information
there are builtin ways to handle path manipulation. [grin]
the simplest would be to use the .Parent property of the DirInfo object you get back from Get-ChildItem or from Get-Item when run against a filesystem. that likely cannot be used since it seems you only have strings to work with.
the next easiest is to use Split-Path and the -Parent switch to jump up one level at a time. that is what the below code does.
$SamplePathList = #(
'\\ad.domain.com\corp\technology\test\information\software'
'\\ad.domain.com\corp\technology\payer information\extracts\item load'
)
$StopAt = '\\ad.domain.com\corp\technology'
$Results = foreach ($SPL_Item in $SamplePathList)
{
$TempPath = $SPL_Item
# send starting path to $Results
$TempPath
while ($TempPath -ne $StopAt)
{
$TempPath = Split-Path -Path $TempPath -Parent
# send to $Results
$TempPath
}
}
$Results
output ...
\\ad.domain.com\corp\technology\test\information\software
\\ad.domain.com\corp\technology\test\information
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\payer information\extracts\item load
\\ad.domain.com\corp\technology\payer information\extracts
\\ad.domain.com\corp\technology\payer information
\\ad.domain.com\corp\technology

Cannot figure out why part of my copy commands are working and some are not

Code is at the bottom. Simply put, when I run the code, my .ps1 files get moved no problem, but for some reason any file with "Lec" as a part of its name will pop up this error message:
cp : Cannot find path 'C:\Users\Administrator\Documents\Win213x_Lec_filename.docx' because it does not
exist.
I do not understand why this is happening when it recognizes the file name, and I double checked the exact file is in the directory with the exact name, but my ps1 files have no issue.
$sub1 = "Lectures"
$sub2 = "Labs"
$sub3 = "Assignment"
$sub4 = "Scripts"
$DirectoryName = "test"
$Win213CopyFiles = ls C:\Users\Administrator\Documents\Win213Copy
$count = 0
foreach ($i in $Win213CopyFiles)
{
if ($i -match ".*Lec.*")
{
cp $i C:\Users\Administrator\Documents\test\Lectures
$count = $count + 1
}
elseif ($i -match ".*Lab.*")
{
cp $i C:\Users\Administrator\Documents\$DirectoryName\$sub2
$count = $count + 1
}
elseif ($i -match ".*Assign.*")
{
cp $i C:\Users\Administrator\Documents\$DirectoryName\$sub3
$count = $count + 1
}
elseif ($i -match ".*.ps1")
{
cp $i C:\Users\Administrator\Documents\$DirectoryName\$sub4
$count = $count + 1
}
Write-host "$i"
}
## Step 9: Display a message "<$count> files moved"
###################################
Write-host "$count files moved"
Copy-Item expects String as input. So it calls the ToString() method of the FileInfo object $i. That returns only the file name, not full path. As source directory is not specified the current working directory is used. Solution is to use full path found in fullname property:
cp $i.fullname C:\Users\Administrator\Documents\test\Lectures
From pipeline Copy-Item can handle FileInfo objects correctly, using the fullname property. Which you should remember not to drop if using Select-Object.

Split and add text/number to the filename

I have been trying to write a script to remove end part of the filename and replace it with a version number of a build. I have tried trim and split but because of extra dots and not good with regex, im having problems.
These are file examples:
Filename.Api.sow.0.1.1856.nupkg
something.Customer.Web.0.1.1736.nupkg
I want to remove 0.1.18xx from these filenames and add a build number from variable. which would be something like 1.0.1234.1233 (major.minor.build.revision)
so the end result should be:
Filename.Api.sow.1.0.1234.1233.nupkg
something.Customer.Web.1.0.112.342.nupkg
Here is my try to split and then rename. But it doesnt work.
$files = Get-ChildItem -Recurse | where {! $_.PSIsContainer}
foreach ($file in $files)
{
$name,$version = $file.Name.Split('[0-9]',2)
Rename-Item -NewName "$name$version" "$name".$myvariableforbuild
}
You are almost there. Here a solution with regex:
$myVariableForBuild = '1.0.1234.1233'
Get-ChildItem 'c:\your_path' -Recurse |
where {! $_.PSIsContainer} |
Rename-Item -NewName { ($_.BaseName -replace '\d+\.\d+\.\d+$', $myVariableForBuild) + $_.Extension }
Probably not the most concise way to do it, but I would split the string based on ".", get the last element of the array (the file extension), then iterate through each array element. If its non-numeric append it to a new string, if numeric break the loop. Then append the new version and file extension to the new string.
$str = "something.Customer.Web.0.1.1736.nupkg"
$arr = $str.Split(".")
$extension = $arr[$arr.Count - 1]
$filename = ""
$newversion = "1.0.112.342"
for ($i = 0 - 1; $i -lt $arr.Count; $i++)
{
if ($arr[$i] -notmatch "^[\d\.]+$")
{
# item is not numeric - add to string
$filename += $arr[$i] + "."
}
else
{
# item is numeric - end loop
break
}
}
# add the new version
$filename += $newversion + "."
# add the extension
$filename += $extension
Obviously its not a complete solution to your problem, but there is enough there for you to get going.