Access a PowerShell Variable Immediately After Mutation - powershell

I have a simple PowerShell script that bulk renames files and I'd like to have a table output that shows 'Old File Name' and 'New File Name' to show the changes. How do I access/reference the new file name while still inside the 'ForEach-Object' loop, assuming it can be done? Can I refresh that particular value of $_? I don't want to just use my variable $newname because I created that and since this is supposed to be output that shows the file names were actually changed, I'd like to access the file's new name from the system as determined by the script.
Write-Host "Old Name `t`t`t`t`t`t New Name"
Write-Host "-------- `t`t`t`t`t`t --------"
$files = Get-ChildItem -Path C:\Users\<user>\Desktop\Config
$files | % ({
if ((!$_.PsIsContainer) -and ($_.Extension -eq '')) {
$oldname = $_.FullName
$newname = "$($_.FullName).cfg"
Rename-Item $oldname $newname
# $_.Name still refers to the old file name without the extension
# How do I immediately access its new name (including extension)?
Write-Host $_.Name `t`t`t`t`t`t $newname
}
})

You can do a Get-Item on $newname:
(Get-Item $newname).Name

I have taken the liberty to rewrite your Script a little, using a Hash Table and New-Object.
To explain what I'm doing here, I will split it a bit up for you.
First I declar some Variables, I'm gonna use in my foreach loop, with this:
Note: -File parameter, is so it will only take file names, if you like to rename everything, files and folder, then remove it.
$files = Get-ChildItem -Path "C:\Users\dha\Desktop\PS test folder" -File
$OldFiles = $files
[int]$i = "0"
Then I'm taking each file one by one, and creating the new name in:
$Newname = ("Blop" + "$i" + ".txt")
And then take the file info for one file, and pipe it to rename-item here
$File | Rename-Item -NewName $Newname
The $ixx is just a plusser so increase the file number by one for each time the foreach loop runs.
Then I'm writing a Hash Table with $Old_And_New variable.
Last, I'm creating a Object with New-object and outputting it.
I Hope this help's and my explanation is understandable.
To script assembled:
$files = Get-ChildItem -Path "C:\Users\dha\Desktop\PS test folder" -File
$OldFiles = $files
[int]$i = "0"
foreach ($File in $files)
{
$Newname = ("Blop" + "$i" + ".txt")
$File | Rename-Item -NewName $Newname
$i++
$Old_And_New = #{
'Old File Name' = "$OldFiles";
'New File Name' = "$Newname"
}
$obj = New-Object -TypeName PSObject -Property $Old_And_New
Write-Output $obj
}

Related

Fix script to allow a scanning of all fields for every row to find if that field contains a target folder name

This is an deepening extension to solve a previously question
Since the input csv file is inconsistent, I cannot use that in this way.
The folder to move to is not always in the same column, so any code trying to use that as input will hit the problem of the value not corresponding to the folder I want to move to. It simply reads garbage ("spam") where it expects the folder name.
The only way to do it is by examining all fields for every row to find if that field contains a target folder name you can use. That means a LOT of Test-Path lines
This is the incriminated code part
Foreach ($fileName in $_.Group.FileName) {
$ValidFileName = $filename -replace $invalid
$targetFile = Join-Path -Path $TargetFolder -ChildPath $fileName
Error is explicitly telling it to move those folders when I iterate through the FileName column. Foreach ($fileName in $_.Group.FileName) {.. this is reading: (1959 10) Showcase Presents n 22,(1959 12) Showcase Presents n 23,alfa, da definire.
My request: Since that examination of all fields for every row is required so I ask for a code editing of this script. Also if this means a LOT of Test-Path lines I suppose that there is no alternative.
However this script below don't create folders and move anything so my request is to try to fix it
$csvpath = 'C:\temp\temp.csv'
$invalid = "[{0}]" -f [RegEx]::Escape(([IO.Path]::GetInvalidFileNameChars() -join ''))
$sourcePath = 'C:\temp\'
Import-Csv C:\temp\temp.csv -Header Title,FileName,Link -Delimiter ';' |
Group-Object Title |
Foreach {
# I prefer to add a trailing slash to folder names
$TargetFolder = Join-Path -Path $sourcePath -ChildPath (($_.Name -replace $invalid)+'\')
# We don't have to create the new folders, because -Force will create them for us
Foreach ($fileName in $_.Group.FileName) {
$ValidFileName = $filename -replace $invalid
$targetFile = Join-Path -Path $TargetFolder -ChildPath $fileName
# Write your values to the console - Make sure the folder is what it should be
Write-Output "Moving '$targetFile' to '$TargetFolder'"
Move-Item $targetFile $TargetFolder -Force -WhatIf
}
}
I was hesitant to even comment on this since you're just not taking any of our advise on all the previoous posts, and just repeating what we say in a new question. We are here to help with your code, and not write it for you but, learning isn't a crime so I will just assume you're just unfamiliar with powershell in general.
$csvpath = 'C:\tomp\temp.csv'
#$invalid = "[{0}]" -f [RegEx]::Escape(([IO.Path]::GetInvalidFileNameChars() -join ''))
$sourcePath = 'C:\tomp\'
Import-Csv $csvpath -Header Title,FileName,Link -Delimiter ',' |
Group-Object Title |
ForEach-Object `
-Begin {
$FoldersLocation = Get-ChildItem -Path C:\tomp -Directory
$Source_Folder = ($FoldersLocation.Parent.FullName | Select-Object -Unique)
#Create array list to hold both columns
[System.Collections.ArrayList]$CombinedRows = #()
} `
-Process {
# Create folder if it doesn't exist
$Destination = Join-Path -Path $Source_Folder -ChildPath $_.Name
if ((Test-Path -Path $Destination) -eq $false) {
"Created: $Destination"
#$null = New-Item -Path ($FoldersLocation.Parent.FullName | Select -Unique) -Name $_.Name -ItemType Directory
}
#region Combine two columns
Foreach ($fileName in $_.Group.FileName) {
$null = $CombinedRows.Add($fileName)
}
Foreach ($link in $_.Group.Link) {
$null = $CombinedRows.Add($link)
}
#endregion
} `
-End {
Foreach ($name in $FoldersLocation) {
if ($name.Name -in $CombinedRows) {
if ($name.Name -match "Showcase"){
# No need for output when supplying the -verbose switch
"Moving: '$($Name.FullName)' to 'C:\tomp\Lanterna Verde - Le Prime Storie'"
# Move-Item -Path $Name.FullName -Destination "C:\tomp\Lanterna Verde - Le Prime Storie"
}
else {
# No need for output when supplying the -verbose switch
"Moving: '$($Name.FullName)]' to 'C:\tomp\Batman DC'"
# Move-Item -Path $Name.FullName -Destination "C:\tomp\Batman DC"
}
}
}
}
All it comes down to is control of flow logic. If a certain condition is met, then do this, or do something else if its not. Added very few inline comments as most of it pretty self explanatory.
I recommend reading up on some powershell instead of hoping others could do everything for you.
Powershell

Powershell script to copy old files and create shortcuts

I am working on creating a stubbing script using powershell. My intentions for this script is to copy any data that hasn't been written to in the past x time period to a new location, check that the file copied, create a shortcut to the "archived" file in it's original location, and eventually delete the old file (I haven't written this section yet). Below is what I have so far. The issue I am having now is that a shortcut is created however all of the shortcuts are saved to the C:\Temp directory and not in any subfolders; if that is where the original file was stored. I think my issue is with $link and I need to split the path's and join them but I am not sure. Any assistance is greatly appreciated!
# Variables
$Original = "C:\Temp"
$Archive = "\\data\users\Temp"
Get-ChildItem -Path $Original -Recurse |
Where-Object {
$_.LastWriteTime -lt [datetime]::Now.AddMinutes(-1)
} | Copy-Item -Destination $Archive -Recurse -force
$sourceFiles = Get-ChildItem $Original -Recurse | Where-Object { $_.LastWriteTime -lt [datetime]::Now.AddMinutes(-1) } | Select Name, Fullname
$destFiles = Get-ChildItem $Archive -Recurse | Select Name, Fullname
$Comparison = Compare-Object $sourceFiles.name $destFiles.name
If ($comparison.sideindicator -ne "<=") {
get-childitem $Archive -Recurse | where { $_.PsIsContainer -eq $false } | ForEach-Object {
$path = '"' + $_.FullName + '"'
$link = $Original + '\' + $_.Basename + '.lnk'
$wshell = New-Object -ComObject WScript.Shell
$shortcut = $wshell.CreateShortcut($link)
$shortcut.TargetPath = $path
$shortcut.Save()
}
}
If ($comparison.sideindicator -eq "<=") {
$comparison.inputobject, $sourceFiles.fullname | Out-File 'C:\ScriptLogs\stubbing.csv' -Force
}
This is the problematic code:
$link = $Original + '\' + $_.Basename + '.lnk'
Basename is only the filename portion without the path, so the .lnk files end up directly in the top-level directory.
You have to use the relative path like this:
$RelativePath = [IO.Path]::GetRelativePath( $Archive, $_.FullName )
$link = [IO.Path]::ChangeExtension( "$Original\$RelativePath", 'lnk' )
This requires .NET 5 for GetRelativePath. To support older .NET versions you can use:
Push-Location $Archive
try { $RelativePath = Resolve-Path -Relative $_.FullName }
finally { Pop-Location }
Resolve-Path -Relative uses the current location as the base base. So we use Push-Location to temporarily change the current location. The try / finally is used to ensure restoring of the original location even in case Resolve-Path throws an exception (e. g. when you have set $ErrorActionPreference = 'Stop').
Although the code might work like this, I think it could be refactored like this:
A single Get-ChildItem call to store the files to be moved into an array (your current $sourceFiles = ... line)
A foreach loop over this array to move each file and create the shortcut.
I think the Compare-Object isn't even necessary, but maybe I'm missing something.
Currently you are iterating over the same directories multiple times, which isn't very efficient and a lot of duplicate code.

How do I create a new files automatically depend on an existing variable in PowerShell?

I have many file in a folder, I would like to check the existing and matching of the file with variable that I initialize. Then, if the file exit and match, I want to get some information from the files (many file), then create a new file depend on how many file exist and match.
I tried this code, I can check the matching and existing file. I can create a new file and get the information from the file, but I only can create 1 file.
The information that I get from the file, each file is different.
$ID = "123"
$Pre = "ABC"
$Path = "C:\Folder"
$PO = Get-ChildItem -Path $Path
foreach ($File in $PO) {
if (($File.Name -match $ID) -and ($File.Name -match $Pre)) {
Write-Host ">>POfile Found: $File"
} else {
Write-Host ">>Check Again!"
}
}
# CREATE FILE
$Jb_Path = "C:\Folder\Jb"
## GET INFORMATION
$count = 1
$Get_PO = Get-ChildItem -Path $Path\$File -File -Recurse
$POfile = Get-Random -InputObject $Get_PO -Count $count
Write-Host ">>Selected POfile= $POfile"
$FilteredContents = Get-Content $POfile | Where-Object {$_ -like "*;INFO*"}
$Get_INFO = $FilteredContents.Substring(5,2)
## NEW FILE
New-Item -Path $Jb_Path\NEW_$Pre$ID-$Get_INFO.txt -Force
In the section # CREATE FILE you are referencing the variable $File which has the last value iterated in the previous foreach (even if it didn't match the if condition).
Asuming the $Pre is for prefix and comes first in a file name simply do a
Get-ChildItem "$Path\*$Pre*$ID*"
to only get file names for your criteria.
As $File contains only one file name a Get-Random doesn't make sense, especially as it might not contain a line with ;INFO
Assuming the two characters to extract are in front of ;INFO this untested script might do:
$Pre = "ABC"
$ID = "123"
$Path = "C:\Folder"
$Jb_Path= "C:\Folder\Jb"
Get-ChildItem "$Path\*$Pre*$ID*" | Get-Content |
Select-String -Pattern '^.....(..).*;INFO' |
Get-Random | ForEach-Object {
$NewFile = Join-Path $Jb_Path ('NEW_{0}{1}-{2}.txt' -f $Pre,
$ID,$_.Matches.Groups[1].Value)
New-Item -Path $NewFile -ItemType File -Force -WhatIf
}
It will only output what it would do without the -WhatIf parameter.
If no file matching the criteria and RegEx pattern is found it will silently continue.
If my assumptions led me wrong, enhance your question be editing it with more details.

PowerShell - Loop through files and rename

newbie here. I am trying to write a PowerShell script to:
loop through all files in directory
List item
Get all .pdf files ONLY
Rename them-the file names are long - over 30 chars
-They contain 2 numbers which I need to extract
-Example:
Cumulative Update 11 for Microsoft Dynamics NAV 2018 (Build 25480).pdf ->
RESULT : = 18CU11.pdf
I tried examples from bunch of sites and I can't seem to even loop successfully.
Either get an error - that path doesn't exist or that can't rename files as somehow loop gets a filepath and that I can't rename
Get-ChildItem "C:\Users\******\Desktop\PowerShell Practice" -Filter *.pdf | #create list of files
ForEach-Object{
$oldname = $_.FullName;
$newname = $_.FullName.Remove(0,17);
#$newname = $_.FullName.Insert(0,"CU")
Rename-Item $oldname $newname;
$oldname;
$newname; #for testing
}
That's just latest attempt, but any other ways of doing it will be fine - as long as it does the job.
Try this logic:
[string]$rootPathForFiles = Join-Path -Path $env:USERPROFILE -ChildPath 'Desktop\PowerShell Practice'
[string[]]$listOfFilesToRename = Get-ChildItem -Path $rootPathForFiles -Filter '*.PDF' | Select-Object -ExpandProperty FullName
$listOfFilesToRename | ForEach-Object {
#get the filename wihtout the directory
[string]$newName = Split-Path -Path $_ -Leaf
#use regex replace to apply the new format
$newName = $newName -replace '^Cumulative Update (\d+) .*NAV 20(\d+).*$', '$2CU$1.pdf' # Assumes a certain format; if the update doesn't match this expectation the original filename is maintained
#Perform the rename
Write-Verbose "Renaming '$_' to '$newName'" -Verbose #added the verbose switch here so you'll see the output without worrying about the verbose preference
Rename-Item -Path $_ -NewName $newName
}
Check the Help for Rename-Item. The Parameter -NewName requires the name of the file only, not the full path.
Try out this:
Get-ChildItem "C:\Users\******\Desktop\PowerShell Practice-Filter" -Filter *.pdf | #create list of files
ForEach-Object{
$oldname = $_.FullName
$newname = $_.Name.Remove(0,17)
Rename-Item -Path $oldname -NewName $newname
$oldname
$newname #for testing
}
Please try this
Get-ChildItem -Path "C:\Users\******\Desktop\PowerShell Practice-Filter" -Filter *.pdf | Rename-Item -NewName $newname

In power shell, how to replicate the action I do in a folder to other folders?

The action I am supposed to do:
Read a file from a folder
Extract a date from the 1st line
Change it to 'yyyymmdd' format
Rename all the file by removing first 4 characters and place this format date at the beginning.
Example FILE.FILE will be changed to 20180725.FILE (The date will be in the file)
I am able to accomplish this to a single folder in Newpath by the following code:
$path="\\Data\DEV\DevDat\Arun\DXSL\Newpath\20170601"
$files=Get-ChildItem $path | Select-Object -first 1
$data = Get-Content "$path\$files" -first 1
foreach($line in $data)
{
$arr = $line.substring(52,4)+$line.substring(46,2)+$line.substring(49,2)
}
get-childitem $path | rename-item -newname { $arr +[string]($_.name).substring(4)}
However, I am not able to replicate this action to the other folders in a loop. The Newpath folder has several sub-folders. I need to rename the files inside each sub-folder inside Newpath. Is there any way to achieve this?
FYI I'm using Version 4 of powershell.
A PowerShell script as suggested in my comment.
It does not check if the file to rename to already exists.
The Rename-Item has the parameer -WhatIf appended, so it only shows what would be done.
If there are more numbers in the first line, the script will match the first pattern.
The script also does not check if the file already is renamed to the pattern.
## Q:\Test\2018\07\25\SO_51522205.ps1
#Requires -Version 3.0
$BasePath = "\\Data\DEV\DevDat\Arun\DXSL\Newpath"
$RE = [RegEx]'(?<Month>\d{2}).(?<Day>\d{2}).(?<Year>\d{4})'
ForEach ($Folder in (Get-ChildItem -Path "$BasePath\*" -Directory)){
ForEach ($File in (Get-ChildItem -Path "$($Folder.FullName)\*" -File)){
$Line1 = (Get-Content $File.FullName | Select-Object -First 1)
If ($File.BaseName.Length -gt 4){
$BaseName = $File.BaseName.SubString(4)
} else {
$BaseName = ''
}
If ($Line1 -match $RE){
$NewName = ("{0}{1}{2}{3}{4}" -f `
$Matches.Year,
$Matches.Month,
$Matches.Day,
$BaseName,
$File.Extension)
$File | Rename-Item -NewName $NewName -WhatIf
} Else {
"{0} doesn't have a proper date in 1st line" -f $File.FullName
}
}
}
Sample tree before,
> tree /F
└───Newpath
└───20170601
blahblah.txt
FILE.FILE
after running the script.
> tree /F
└───Newpath
└───20170601
20180724blah.txt
20180725.FILE