Storing and Resetting LastModifiedDate for a file - powershell

I am attempting to add a text string to a file without updating the LastModifiedDate property. Is it possible to set the property to a variable before modifying the file, then reset the property to the stored value in the variable? I am attempting to do this on a folder containing hundreds or possibly thousands of files.
I think I have the right syntax to add the line of text I need and to set the last modified date to a variable but I'm not sure what syntax I need to reset the modified date in the for each loop.
Get-ChildItem C:\Temp -Name *.bnf* -Recurse |Out-file C:\Temp\list.txt
cd C:\Temp
$FileList = Get-Content C:\Temp\Grammarlist.txt
(Get-Content $FileList) |
Foreach-Object {
$temp = Get-Item $_.LastWriteTime}
$_ # send the current line to output
if ($_ -match "// Version")
{
#Add Lines after the selected pattern
"// Release 19.1.4"
}
} | Set-Content $FileList

I would suggest using the read/write LastWriteTime property of the file.
Adapted from ss64.com:
$oldDate = (dir test.txt).LastWriteTime
echo asdf >> test.txt
(dir test.txt).LastWriteTime = $oldDate

Related

How to select [n] Items from a CSV list to assign them to a variable and afterwards remove those items and save the file using PowerShell

I'm parsing a CSV file to get the names of folders which I need to copy to another location. Because there are hundreds of them, I need to select the first 10 or so and run the copy routine but to avoid copying them again I'm removing them from the list and saving the file.
I'll run this on a daily scheduled task to avoid having to wait for the folders to finish copying. I'm having a problem using the 'Select' and 'Skip' options in the code (see below), if I remove those lines the folders are copied (I'm using empty folders to test) but if I have them in, then nothing happens when I run this in PowerShell.
I looked around in other questions about similar issues but did not find anything that answers this particular issue selecting and skipping rows in the CSV.
$source_location = 'C:\Folders to Copy'
$folders_Needed = gci $source_location
Set-Location -Path $source_location
$Dest = 'C:\Transferred Folders'
$csv_name = 'C:\List of Folders.csv'
$csv_Import = Get-Content $csv_name
foreach($csv_n in $csv_Import | Select-Object -First 3){
foreach ($folder_Tocopy in $folders_Needed){
if("$folder_Tocopy" -contains "$csv_n"){
Copy-Item -Path $folder_Tocopy -Destination $Dest -Recurse -Verbose
}
}
$csv_Import | Select-Object -Skip 3 | Out-File -FilePath $csv_name
}
It should work with skip/first as in your example, but I cannot really test it without your sample data. Also, it seems wrong that you write the same output to the csv file at every iteration of the loop. And I assume it's not a csv file but actually just a plain text file, a list of folders? Just folder names or full paths? (I assume the first.)
Anyways, here is my suggested update to the script (see comments):
$source_location = 'C:\Folders to Copy'
$folders_Needed = Get-ChildItem $source_location
$Dest = 'C:\Transferred Folders'
$csv_name = 'C:\List of Folders.csv'
$csv_Import = #(Get-Content $csv_name)
# optional limit
# set this to $csv_Import.Count if you want to copy all folders
$limit = 10
# loop over the csv entries
for ($i = 0; $i -lt $csv_Import.Count -and $i -lt $limit; $i++) {
# current line in the csv file
$csv_n = $csv_Import[$i]
# copy the folder(s) which name matches the csv entry
$folders_Needed | where {$_.Name -eq $csv_n} | Copy-Item -Destination $Dest -Recurse -Verbose
# update the csv file (skip all processed entries)
$csv_Import | Select-Object -Skip ($i + 1) | Out-File -FilePath $csv_name
}

Powershell: Logging foreach changes

I have put together a script inspired from a number of sources. The purpose of the powershell script is to scan a directory for files (.SQL), copy all of it to a new directory (retain the original), and scan each file against a list file (CSV format - containing 2 columns: OldValue,NewValue), and replace any strings that matches. What works: moving, modifying, log creation.
What doesn't work:
Recording in the .log for the changes made by the script.
Sample usage: .\ConvertSQL.ps1 -List .\EVar.csv -Files \SQLFiles\Rel_1
Param (
[String]$List = "*.csv",
[String]$Files = "*.sql"
)
function Get-TimeStamp {
return "[{0:dd/MM/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
}
$CustomFiles = "$Files\CUSTOMISED"
IF (-Not (Test-Path $CustomFiles))
{
MD -Path $CustomFiles
}
Copy-Item "$Files\*.sql" -Recurse -Destination "$CustomFiles"
$ReplacementList = Import-Csv $List;
Get-ChildItem $CustomFiles |
ForEach-Object {
$LogFile = "$CustomFiles\$_.$(Get-Date -Format dd_MM_yyyy).log"
Write-Output "$_ has been modified on $(Get-TimeStamp)." | Out-File "$LogFile"
$Content = Get-Content -Path $_.FullName;
foreach ($ReplacementItem in $ReplacementList)
{
$Content = $Content.Replace($ReplacementItem.OldValue, $ReplacementItem.NewValue)
}
Set-Content -Path $_.FullName -Value $Content
}
Thank you very much.
Edit: I've cleaned up a bit and removed my test logging files.
Here's the snippet of code that I've been testing with little success. I put the following right under $Content= Content.Replace($ReplacementItem.OldValue, $ReplacementItem.NewValue)
if ( $_.FullName -like '*TEST*' ) {
"This is a test." | Add-Content $LogFile
}
I've also tried to pipe out the Set-Content using Out-File. The outputs I end up with are either a full copy of the contents of my CSV file or the SQL file itself. I'll continue reading up on different methods. I simply want to, out of hundreds to a thousand or so lines, to be able to identify what variables in the SQL has been changed.
Instead of piping output to Add-Content, pipe the log output to: Out-File -Append
Edit: compare the content using the Compare-Object cmdlet and evaluate it's ouput to identify where the content in each string object differs.

How to add file name to (beginning of) a text file?

For over 800 files I need information that's in the file name to be included in the contents of the text file (actually .md files).
The file names are always of the same structure, something like 0000-title-text-1-23.md; only the 1-23 part changes (and that is the information I need).
I am a novice as it comes to scripting, but I figured out that this should be an easy task for PowerShell — yet I don't get it working the way I want. What did come closest:
Get-Childitem "C:\PATH\*.md" | ForEach-Object{
$fileName = $_.BaseName
Add-Content -Path .\*.md -Value $fileName
}
But that adds all file names in the directory, not just the one from the file itself.
What am I doing wrong?
Use this code to do what you exactly want,
it will get the last 2 parts of your filename and
put it in the beginning of your file content.
Get-Childitem "C:\PATH\*.md" | ForEach-Object{
$fileNameParts = ($_.BaseName).split('-')
$info = $fileNameParts[-2] + '-' + $fileNameParts[-1]
$info + (Get-Content $_ -Raw) | Set-Content $_
}
Something like this would work although it does add the content to the end of the file:
#Get all the .txt or .md files in your location
Get-ChildItem -Filter "*.txt" | Foreach-Object{
#Get the base name of the file
$baseName = $_.BaseName
#Split the base name
$array = $baseName -Split '-'
#Put the third and fourth element in the array into a separate variable
#This will be added to the file
$addToFile = $array[3] + '-' + $array[4]
#Add the $addToFile variable to the file
Add-Content $_.FullName -Value $addToFile
}

Search and replace files and folders names with txt file support

I have many folders and inside these different files. Each folder and their children files have the same name and different extension, so in the ABC folder there are the ABC.png, ABC.prj, ABC.pgw files, in the DEF folder there are the DEF.png, DEF.prj, DEF.pgw files and so on.
With a script I have created a txt file with the list of png file names. Then I put in row 2 a new name for the name in row1, in row 4 a new name for the name in row 3, and so on.
Now I'm searching a powershell script that:
- scan all folder for the name in row 1 and replace it with name in row2
- scan all folder for the name in row 3 and replace it with name in row4 and so on
I have try with this below, but it doesn't work.
Have you some suggestions? Thank you
$0=0
$1=1
do {
$find=Get-Content C:\1\Srv\MapsName.txt | Select -Index $0
$repl=Get-Content C:\1\Srv\MapsName.txt | Select -Index $1
Get-ChildItem C:\1\newmaps -Recurse | Rename-Item -NewName { $_.name -replace $find, $repl} -verbose
$0=$0+2
$1=$1+2
}
until ($0 -eq "")
I believe there are several things wrong with your code and also the code Manuel gave you.
Although you have a list of old filenames and new filenames, you are not using that in the Get-ChildItem cmdlet, but instead try and replace all files it finds.
Using -replace uses a Regular Expression replace, that means the special character . inside the filename is regarded as Any Character, not simply a dot.
You are trying to find *.png files, but you do not add a -Filter with the Get-ChildItem cmdlet, so now it will return all filetypes.
Anyway, I have a different approach for you:
If your input file C:\1\Srv\MapsName.txt looks anything like this:
picture1.png
ABC_1.png
picture2.png
DEF_1.png
picture3.png
DEF_2.png
The following code will use that to build a lookup Hashtable so it can act on the files mentioned in the input file and leave all others unchanged.
$mapsFile = 'C:\1\Srv\2_MapsName.txt'
$searchPath = 'C:\1\NewMaps'
# Read the input file as an array of strings.
# Every even index contains the file name to search for.
# Every odd index number has the new name for that file.
$lines = Get-Content $mapsFile
# Create a hashtable to store the filename to find
# as Key, and the replacement name as Value
$lookup = #{}
for ($index = 0; $index -lt $lines.Count -1; $index += 2) {
$lookup[$lines[$index]] = $lines[$index + 1]
}
# Next, get a collection of FileInfo objects of *.png files
# If you need to get multiple extensions, remove the -Filter and add -Include '*.png','*.jpg' etc.
$files = Get-ChildItem -Path $searchPath -Filter '*.png' -File -Recurse
foreach ($file in $files) {
# If the file name can be found as Key in the $lookup Hashtable
$find = $file.Name
if ($lookup.ContainsKey($find)) {
# Rename the file with the replacement name in the Value of the lookup table
Write-Host "Renaming '$($file.FullName)' --> $($lookup[$find])"
$file | Rename-Item -NewName $lookup[$find]
}
}
Edit
If the input text file 'C:\1\Srv\MapsName.txt' does NOT contain filenames including their extension, change the final foreach loop into this:
foreach ($file in $files) {
# If the file name can be found as Key in the $lookup Hashtable
# Look for the file name without extension as it is not given in the 'MapsName.txt' file.
$find = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
if ($lookup.ContainsKey($find)) {
# Rename the file with the replacement name in the Value of the lookup table
# Make sure to add the file's extension if any.
$newName = $lookup[$find] + $file.Extension
Write-Host "Renaming '$($file.FullName)' --> '$newName'"
$file | Rename-Item -NewName $newName
}
}
Hope that helps
The problem in your snippet is that it never ends.
I tried it and it works but keeps looping forever.
I created a folder with the files a.txt, b.txt and c.txt.
And in the map.txt I have this content:
a.txt
a2.md
b.txt
b2.md
c.txt
c2.md
Running the following script I managed to rename every file to be as expected.
$0=0
$1=1
$find=Get-Content D:\map.txt | Select -Index $0
while($find) {
$find=Get-Content D:\map.txt | Select -Index $0
$repl=Get-Content D:\map.txt | Select -Index $1
if(!$find -Or !$repl) {
break;
}
Get-ChildItem D:\Files -Recurse | Rename-Item -NewName { $_.name -replace $find, $repl} -verbose
$0=$0+2
$1=$1+2
}

Powershell capture first filename from a folder

Newbie to powershell
I need to capture first file name from a directory. However my current script captures all file names. Please suggest changes to my code below.
# Storing path of the desired folder
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path -Force -Recurse
$contents.Name
The result is following
test-01.eof
test-02.eof
test-03.eof
i just want one file (any) from this list. So expected result should be
test-01.eof
You could use Select-Object with the -first switch and set it to 1
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path -Force -Recurse -File | Select-Object -First 1
I've added the -File switch to Get-ChildItem as well, since you only want to return files.
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path -Force -Recurse
$contents # lists out all details of all files
$contents.Name # lists out all files
$contents[0].Name # will return 1st file name
$contents[1].Name # will return 2nd file name
$contents[2].Name # will return 3rd file name
The count starts from 0. So $contents here is an array or a list and whatever integer you mention in [] is the position of the item in that array/list. So when you type $contents[9], you are telling powershell that get the 9th item from the array $contents. This is how you iterate through a list. In most programming languages the count begins from 0 and not 1. It is a bit confusing for a someone who is entering the coding world but you get used to it.
Please use below command which is simple and helpful. Adding recurse will only put slight load on the machine or powershell (when the code is huge and it has been used somewhere)
Storing path of the desired folder
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path | sort | Select-Object -First 1
$contents.Name
Output Will be as as expected:
test-01.eof
Select-Object -First will select each object (or row) and provide first row data as an output if you put as 1