Renaming files Powershell, Cannot rename file does not exist - regex

I try to remove " - 13234" from a bunch of files recursively. And if the file is already present after removing that part it should add (1), (2) and so on after the new file name.
I tested it on a small set of files and that was working fine. Now I tried it on a larger test set and it gave me some errors.
Like: Cannot rename file does not exist
Like: Cannot create a file when file already exist
For a lot of files it looks like it is doing fine actually.
$files = Get-ChildItem <location> -recurse -file | sort name | group-object -property {$_.fullname -replace "(.*)-.*?(\..*?)$",'$1$2'}
$files | foreach {
$inc = 0
if ($_.count -gt 1) {
rename-item -literalpath $_.group[0].fullname -NewName (($_.group[0].basename -replace "(.*)-.*?$",'$1') + $_.group[0].extension)
$inc++
for ($i = $inc; $i -lt $_.count; $i++) {
rename-item -literalpath $_.group[$i].fullname -NewName (($_.group[$i].basename -replace "(.*)-.*?$",'$1') + "($i)" + $_.group[$i].extension)
}
}
else {
rename-item -literalpath $_.group.fullname -NewName (($_.group.basename -replace "(.*)-.*?$",'$1') + $_.group.extension)
}
}

If the following files are in the same directory, the script probably will not work.
1 - 13234.txt
1.txt
The script first tries to change "1 - 13234.txt" to "1. txt".
You had better first check the existence of the file.
Get-ChildItem -File -Recurse |
where BaseName -match "(.*)-" |
Group-Object { $_.DirectoryName + "\" + $Matches[1].TrimEnd() + $_.Extension } |
foreach {
$fi = [IO.FileInfo]::new($_.Name)
$i = 0
$_.Group | Rename-Item -NewName {
do {
$newName = $fi.DirectoryName + "\" + $fi.BaseName + $(if($i){"($i)"}) + $fi.Extension
$script:i++
} while (Test-Path $newName)
$newName
} -PassThru
}

I would do something like this:
$Path = '<PATH TO THE ROOTFOLDER WHERE THE FILES TO RENAME ARE FOUND>'
Get-ChildItem -Path $Path -Filter '*-*' -File -Recurse | ForEach-Object {
# obtain the new file basename
$newBaseName = ($_.BaseName -split '-')[0].Trim()
$extension = $_.Extension # this includes the dot
$folder = $_.DirectoryName
# get an array of all filenames (name only) of the files with a similar name already present in the folder
$allFiles = #(Get-ChildItem $folder -Filter "$newBaseName*$extension" -File | Select-Object -ExpandProperty Name)
# for PowerShell version < 3.0 use this
# $allFiles = #(Get-ChildItem $folder-Filter "$newBaseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename
$newFileName = $newBaseName + $extension
if ($allFiles.Count) {
$count = 1
while ($allFiles -contains $newFileName) {
$newFileName = "{0}({1}){2}" -f $newBaseName, $count++, $extension
}
}
Write-Host "Renaming '$($_.FullName)' to '$newFileName'"
$_ | Rename-Item -NewName $newFileName -Force
}
Hope that helps

Related

Compare size of multiple subdirectory before and after a break in a Powershell script

I'm a beginner with Powershell, also forgive my English which isn't the best.
I have a directory with several subdirectories like this
Directory
My goal is to target directory that are updated while the script is running. So I made this script
$path = "C:\Users\s611284\Desktop\archive"
#Check that the directories have the correct name and calculate their size
Get-ChildItem -Force $path -ErrorAction SilentlyContinue -Directory | foreach {
$Size = (Get-ChildItem $_.fullname -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum/ 1Kb
$FolderName = $_.BaseName -match '1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})' -or $_.BaseName -match '1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})'
$Folder = $_.BaseName
if ($FolderName -eq "true") {
write-host(" name $Folder is correct, $Size Kb")
}
else {
write-host( "name $Folder is incorrect")
}
}
#Break
Start-Sleep -Seconds 30
write-host( "end of break")
#directory size calculation after the break
Get-ChildItem -Force $path -ErrorAction SilentlyContinue -Directory | foreach {
$Size1 = (Get-ChildItem $_.fullname -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum/ 1Kb
$FolderName1 = $_.BaseName -match '1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})' -or $_.BaseName -match '1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})'
$Folder1 = $_.BaseName
if ($FolderName1 -eq "true") {
write-host("name $Folder1 is correct, $Size1 Kb")
}
else {
write-host( "name $Folder1 is incorrect")
}
}
All of this is working great
So now I want to compare the size of the subdirectories before and after the break, to know which have been updated
I tried
if ( $FolderSize -eq $FolderSize1 )
{
Write-Output $True
}
Else
{
Write-Output $False
}
at the end of my second block but it isn't working..
I also tried Compare-object but I don't think this command will help in my case
I hope you guys will understand my post and help me
Thanks !
If I understood correctly, you're looking to filter those folders where it's Size has changed after 30 seconds, if that's the case, you could use a function so that you don't need to repeat your code. You can make your function return a hash table where the Keys are the folder's absolute path and the Values are their calculated size, once you have both results (before 30 seconds and after 30 seconds) you can run a comparison against both hash tables outputting a new object with the folder's Absolute Path, Size Before and Size After only for those folders where their calculated size has changed.
function GetFolderSize {
[cmdletbinding()]
param($path)
$map = #{}
Get-ChildItem $path -Directory -Force | ForEach-Object {
$Size = (Get-ChildItem $_.Fullname -Recurse | Measure-Object -Property Length -Sum).Sum / 1Kb
$FolderName = $_.BaseName -match '1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})' -or $_.BaseName -match '1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})'
if ($FolderName) {
$map[$_.FullName] = $size
}
}
if($map) { $map }
}
$path = "C:\Users\s611284\Desktop\archive"
$before = GetFolderSize $path -ErrorAction SilentlyContinue
Start-Sleep -Seconds 30
$after = GetFolderSize $path -ErrorAction SilentlyContinue
foreach($key in $after.PSBase.Keys) {
if($before[$key] -ne $after[$key]) {
# this is a folder with a different size than before
[PSCustomObject]#{
FullName = $key
SizeBefore = $before[$key]
SizeAfter = $after[$key]
}
}
}
Not to take away from Santiago's helpful answer, but to provide an alternate solution, here's my take:
$path = "C:\Users\s611284\Desktop\archive"
$count = 0
$hashMap = #{}
While ($count -lt 2) {
Get-ChildItem -Path $path -Directory |
ForEach-Object -Begin {
$count++
$toMatch = "1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})|1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})"
} -Process {
$folderMatch = $_.BaseName -match $toMatch
if ($folderMatch) {
$size = (Get-ChildItem -Path $_.FullName -Recurse -EA 0 | Measure-Object -Property "Length" -Sum).Sum / 1kb
if (-not$hashMap.ContainsKey($_.BaseName)) {
$hashMap.Add($_.BaseName,$size)
}
if ($count -ge 2) {
if ($hashMap[$_.BaseName] -ne $size) {
$_.BaseName + " " + "is a different size"
}
}
}
} -End {
if ($count -ne 2) {
Start-Sleep -Seconds 30
}
}
}
Personally, I hate the re-use of code and feel like something can always be done about it if you find yourself repeating code (copy/paste).
My question to you is:
Aren't those folder names pretty unique?
Could you not substitute it for a Wild Card Expression?
i.e.: $_.BaseName -like '*1A version aout2021*'
All in the name of "cleanliness" code. lol

Powershell - Searching for strings (in list) in a word document

I found some code for searching for strings in a Word Document. I altered it to suit my needs (I need to search from a very long list of strings). Unfortunately, I am getting a weird error.
While the script is running, it opens the word document, searches the word document and here is where it gets weird, instead of closing the document and opening the next, it presents me with a 'save as' dialog box and the script hangs until I cancel out of it. When I cancel out of it, my script continues.
Here is the script I'm using, would anyone see where I'm going south?
$results = #{}
Write-Host "Loading getStringMatch into memory" -ForegroundColor DarkMagenta
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
Write-Host "Searching In ... $($File.FullName) " -ForegroundColor DarkYellow
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
If($range.Text -match ".{$($charactersAround)}$($findtext).{$($charactersAround)}"){
$properties = #{
File = $file.FullName
Match = $findtext
TextAround = $Matches[0]
}
$results += #(New-Object -TypeName PsCustomObject -Property $properties)
}
$document.close()
Write-Host "Closing Document ... $($File.FullName) " -ForegroundColor Red
}
#If($results){
# $results | Export-Csv $output -NoTypeInformation
#}
$application.quit()
}
$searchWords=Get-Content "C:\Temp\USDA_Search_For.txt"
Foreach ($sw in $searchWords)
{
Write-Host "Setting Variables ..." -ForegroundColor DarkMagenta
Set-StrictMode -Version latest
$path = "C:\Temp"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "C:\Temp\Found.csv"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First"
$charactersAround = 30
#$results = #{}
$findtext = $sw
Write-Host "Searching For ... $findtext" -ForegroundColor Green
getStringMatch
#clean up stuff
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
If($results){
$results | Export-Csv $output -NoTypeInformation
}
import-csv $output

Powershell parsing xml logfile & get currently parsed filename

I'm new with powershell and in need of guidance. Been scouring the site for answers and coming up blank, decided to ask instead. If this has been answered please refer me to the link.
I have an application log (xml format) like below:
<log><identifier>123axr4x5</identifier><login>USER1</login><source>Order-Management</source><AddlInfo>Execution Time : 20ms</AddlInfo><Exception></Exception><timestamp>01/01/2015:22:00:00</timestamp><serverticks>643670855</serverticks><PID>1234</PID><Machine>PRD01X12mm</Machine></log>
<log><identifier>dd8jksl3g</identifier><login>USER2</login><source>Service-Assurance</source><AddlInfo>Execution Time : 80ms</AddlInfo><Exception></Exception><timestamp>01/01/2015:22:00:00</timestamp><serverticks>643680865</serverticks><PID>1234</PID><Machine>PRD01X12mm</Machine></log>
: and so on
I am creating a log parser that will scan a folder and its subfolder for matching regex pattern, and based on certain threshold, output into gridview/export to CSV. I am almost done, however i'm unable to solve 1 problem, which is to get the filename currently being parsed, to be displayed on the gridview.
Basically i am using piped Get-ChildItem as below
Get-ChildItem $Dir -recurse -Filter *logging*.txt|
Sort-Object LastWriteTime |
?{$_.LastWriteTime -gt (Get-Date).AddMinutes(-60)}|
Select-String -Pattern $Text |
Select-String -Pattern $Text3 |
Select-String -Pattern $Text2 -allmatches |
Foreach-Object {
$information = $_|Select-Object -Property API, Duration,DataRetrieved, ServerTime, ServerTicks , Identifier, Filename
$information.Filename = $_.Name
#$information.Filename = $_.FullName
} |
Out-GridView
Below is the full code:
$Dir = "C:\log\"
$threshold = 1 + 0
$StartTime = (Get-Date).ToString();
$EndTime = (Get-Date).ToString();
$Text = "abc"
$Text2 = "def"
$Text3 = "ghi"
$OutFile = "result"
$OutPath = $Dir + $OutFile + ".txt"
#ExtractionParameters
$AddlInnfoTagBegin = "AddlInfo"
$AddlInnfoTagEnd = "/AddlInfo"
$ServerTimeOfLogTagBegin = "ServerTimeOfLog"
$ServerTimeOfLogTagEnd = "/ServerTimeOfLog"
$ServerTicksTagBegin = "ServerTicks"
$ServerTicksTagEnd = "/ServerTicks"
$IdentifierTagBegin = "Identifier"
$IdentifierTagEnd = "/Identifier"
#parse file in folders
Get-ChildItem $Dir -recurse -Filter *logging*.txt|
Sort-Object LastWriteTime |
#?{$_.LastWriteTime -gt (Get-Date).AddMinutes(-60)}|
Select-String -Pattern $Text |
Select-String -Pattern $Text3 |
Select-String -Pattern $Text2 -allmatches |
Foreach-Object {
# take line and split it at tabulators
$parts = $_.Line
#write $parts
$indexOfAddlInfoBegin = $parts.IndexOf($AddlInnfoTagBegin) + $AddlInnfoTagBegin.Length +1
$indexOfAddlInfoEnd = $parts.IndexOf($AddlInnfoTagEnd) -1
$AddlInfoData = $parts.Substring($indexOfAddlInfoBegin, $indexOfAddlInfoEnd - $indexOfAddlInfoBegin)
$AddlInfoReplaced = $AddlInfoData.Replace(" seconds ","#")
$AddlInfoSplit = $AddlInfoReplaced.Split('#')
$information = $_|Select-Object -Property API, Duration,DataRetrieved, ServerTime, ServerTicks , Identifier, Filename
#get filename, which does not work
$information.Filename = $_.Name
#$information.Filename = $_.FullName
$information.API = $AddlInfoSplit[0].Split(':')[0]
$information.DataRetrieved = $AddlInfoSplit[1]
$information.Duration = $AddlInfoSplit[0].Split(':')[1]
$information.Duration = $information.Duration.Replace("Execution Time = ","")
$indexOfServerTimeBegin = $parts.IndexOf($ServerTimeOfLogTagBegin) + $ServerTimeOfLogTagBegin.Length +1
$indexOfServerTimeEnd = $parts.IndexOf($ServerTimeOfLogTagEnd) -1
$ServerTimeData = $parts.Substring($indexOfServerTimeBegin, $indexOfServerTimeEnd - $indexOfServerTimeBegin)
$information.ServerTime = $ServerTimeData
$indexOfServerTicksBegin = $parts.IndexOf($ServerTicksTagBegin) + $ServerTicksTagBegin.Length +1
$indexOfServerTicksEnd = $parts.IndexOf($ServerTicksTagEnd) -1
$ServerTickData = $parts.Substring($indexOfServerTicksBegin, $indexOfServerTicksEnd - $indexOfServerTicksBegin)
$information.ServerTicks = $ServerTickData
$indexOfIdentifierBegin = $parts.IndexOf($IdentifierTagBegin) + $IdentifierTagBegin.Length +1
$indexOfIdentifierEnd = $parts.IndexOf($IdentifierTagEnd) -1
$IdentifierData = $parts.Substring($indexOfIdentifierBegin, $indexOfIdentifierEnd - $indexOfIdentifierBegin)
$information.Identifier = $IdentifierData
$DurationAsInt = 0 + $information.Duration
if($DurationAsInt -gt $threshold) {
write $information
}
} |
Out-GridView
#Out-File -FilePath $OutPath -Append -Width 200
Any help is appreciated, thanks!!
-CL
The property you are looking for is "FileName".
$information.Filename = $_.FileName
Powershell provides a cmdlet "Get-Member" which would list all available properties/methods. You could enumerate the members to console and inspect what is available
Write-Host ( $_ | Get-Member)

Replace fist character in filename

I am reading file names and if the file does not start with an N I need to replace that letter with the letter N.
foreach ($item in Get-ChildItem $Path) {
Write-host "working on $item"
if ($item -match "^(N\d{3})") {
# This file will already process correctly
} elseif ($item -match "^(\d{3})") {
$FilePath = $Path + $item
Write-Host $FilePath
#Rename this file so it will process correctly
Rename-Item $FilePath N$item -Force
Write-Host "Renaming: "$item "to N$item"
} elseif ($item -match "^[a-zA-Z](\d{3})") {
#replace first character with "N"
# How do I replace the first letter with an "N"?
}
}
You're thinking too complicated. This should suffice:
Get-ChildItem $Path | Rename-Item -NewName { $_.Name -creplace '^[^N]','N' }
or this, if you don't want those files "renamed" that already start with an uppercase N:
Get-ChildItem $Path |
Where-Object { $_.Name -cmatch '^[^N]' } |
Rename-Item -NewName { $_.Name -creplace '^[^N]','N' }

How to report on stored procedures without unit tests

Our SQL project has a huge number of stored procedures that should all be accompanied by a unit test.
I'm trying to write some Powershell to report on stored procedures that aren't accompanied by a unit test.
Finding the list of stored procedures is trivial - checking they have a test, not so simple. The code seems to be stored in resource files.
This is what I have so far:
$sps = ((gci -recurse -include *.sql | select-string "CREATE PROCEDURE") -split ':' | select-string "CREATE PROCEDURE" | ForEach-Object {$_ -Replace "CREATE PROCEDURE ", ""}) -split " " | select-string "\."
foreach ($sp in $sps)
{
Write-Host $sp
#ToDo: For each stored procedure, report on a count of EXEC statements calling this procedure in .resx files
}
Any suggestions?
After much ado (my Powershell-fu sucks), I've come up with this:
# Only include stored procedures that are in the project files
$prjs = (gci -recurse -include *.sqlproj) | select fullname | select-string ":"
foreach ($prj in $prjs)
{
$file = ($prj -Replace "#{FullName=","") -Replace "}",""
$xml = [xml](Get-Content $file)
$used = (((($xml.Project.ItemGroup.Build) | select Include) -split "\\") | select-string ".sql") -replace "}",""
}
# Pull a list of SPs from the project code
$sps = ((gci -recurse -include $used | select-string "CREATE PROC") -split ':' | select-string "CREATE PROC" | ForEach-Object {$_ -Replace "CREATE PROCEDURE ", ""} | ForEach-Object {$_ -Replace "CREATE PROC ", ""}) -split " " | select-string "\."
$fails = 0
$passes = 0
foreach ($sp in $sps)
{
# Handle disparity in presentation between [schema].[SpName] between SP and test code
$sp2 = ($sp -Replace "\[", "") -Replace "\]", ""
# Count each SP name presentation in test code
$valium1 = (gci -recurse -include *.resx | select-string -SimpleMatch $sp).count
$valium2 = (gci -recurse -include *.resx | select-string -SimpleMatch $sp2).count
# If we don't have a match for either presentation, then no test exists
if ($valium1 -eq 0 -AND $valium2 -eq 0)
{
$fails++;
Write-Host "***FAIL***" $sp
}
else
{
$passes++;
Write-Host "***PASS***" $sp
}
}
$total = [int]$fails + [int]$passes
Write-Host
Write-Host "Passes: " $passes
Write-Host "Fails : " $fails
Write-Host "------------"
Write-Host "Total : " $total
Write-Host "------------"