Store Filename as separate variables in PowerShell - based on specific character - powershell

I have a vendor file that is stored in a specific format and I would like to use PowerShell to convert the file name into three separate variables which I would then use to pass to a stored procedure that would be executed.
The file format would look like:
AA_BBBBB_YYYYMMDD.xlsx
For instance, the following is an example of a current Excel file:
TX_StampingFee_20210303.xlsx
The 1st characters will generally be a US state abbreviation but not always - so it could be 2 or 3 characters long. The 2nd section is the type of fee the Excel file contains while the last section is the date in a YYYYMMDD format.
What I would like to do is have PowerShell read the file name and separate the name into three separate variables:
$State
$Fee
$Date
What I have so far will list the Excel file in the directory and then I tried to split the name up - but I believe the file extension on the name is causing an error. How can I split this file name into separate variables based off the "_" ( underscore ) character?
Foreach ($file in Get-Childitem "C:\PowerShell\Test\*.xlsx") {
$file.name
}
$filenames = (Get-ChildItem -Path "C:\PowerShell\Test\*.xlsx" -Directory).Name
Write-Host $filenames
$chararray = $filenames.Split("_")
$chararray

You can target the basename which is the filename minus the extension. Then when you split into 3 parts you can directly put those into 3 variables.
Foreach ($file in Get-Childitem "C:\PowerShell\Test\*.xlsx") {
$State,$Fee,$Date = $file.basename.split('_')
Write-Host State: $State Fee: $Fee Date: $Date
}

Use BaseName rather than the Name property to get the filename without the extension.
Then, if you trust the pattern of the filenames, you can index into your array with a range to join your 1+ fee type substrings into a single string:
$filenames = (Get-ChildItem -Path "C:\PowerShell\Test\*.xlsx" -File).BaseName
if ($filenames) {
[System.Collections.ArrayList]$fees = #()
[System.Collections.ArrayList]$states = #()
[System.Collections.ArrayList]$dates = #()
foreach ($name in $filenames) {
$chararray = $name.Split("_")
$arrlen = $chararray.length
if ($arrlen -ge 3) {
$states += $chararray[0]
$fees += $chararray[1..$arrlen-2] -join '_'
$dates += $chararray[$arrlen])
}
}
}

Related

Powershell Files fetch

Am looking for some help to create a PowerShell script.
I have a folder where I have lots of files, I need only those file that has below two content inside it:
must have any matching string pattern as same as in file file1 (the content of file 1 is -IND 23042528525 or INDE 573626236 or DSE3523623 it can be more strings like this)
also have date inside the file in between 03152022 and 03312022 in the format mmddyyyy.
file could be old so nothing to do with creation time.
then save the result in csv containing the path of the file which fulfill above to conditions.
Currently am using the below command that only gives me the file which fulfilling the 1 condition.
$table = Get-Content C:\Users\username\Downloads\ISIN.txt
Get-ChildItem `
-Path E:\data\PROD\server\InOut\Backup\*.txt `
-Recurse |
Select-String -Pattern ($table)|
Export-Csv C:\Users\username\Downloads\File_Name.csv -NoTypeInformation
To test if a file contains a certain keyword from a range of keywords, you can use regex for that. If you also want to find at least one valid date in format 'MMddyyyy' in that file, you need to do some extra work.
Try below:
# read the keywords from the file. Ensure special characters are escaped and join them with '|' (regex 'OR')
$keywords = (Get-Content -Path 'C:\Users\username\Downloads\ISIN.txt' | ForEach-Object {[regex]::Escape($_)}) -join '|'
# create a regex to capture the date pattern (8 consecutive digits)
$dateRegex = [regex]'\b(\d{8})\b' # \b means word boundary
# and a datetime variable to test if a found date is valid
$testDate = Get-Date
# set two variables to the start and end date of your range (dates only, times set to 00:00:00)
$rangeStart = (Get-Date).AddDays(1).Date # tomorrow
$rangeEnd = [DateTime]::new($rangeStart.Year, $rangeStart.Month, 1).AddMonths(1).AddDays(-1) # end of the month
# find all .txt files and loop through. Capture the output in variable $result
$result = Get-ChildItem -Path 'E:\data\PROD\server\InOut\Backup'-Filter '*.txt'-File -Recurse |
ForEach-Object {
$content = Get-Content -Path $_.FullName -Raw
# first check if any of the keywords can be found
if ($content -match $keywords) {
# now check if a valid date pattern 'MMddyyyy' can be found as well
$dateFound = $false
$match = $dateRegex.Match($content)
while ($match.Success -and !$dateFound) {
# we found a matching pattern. Test if this is a valid date and if so
# set the $dateFound flag to $true and exit the while loop
if ([datetime]::TryParseExact($match.Groups[1].Value,
'MMddyyyy',[CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::None,
[ref]$testDate)) {
# check if the found date is in the set range
# this tests INCLUDING the start and end dates
$dateFound = ($testDate -ge $rangeStart -and $testDate -le $rangeEnd)
}
$match = $match.NextMatch()
}
# finally, if we also successfully found a date pattern, output the file
if ($dateFound) { $_.FullName }
elseif ($content -match '\bUNKNOWN\b') {
# here you output again, because unknown was found instead of a valid date in range
$_.FullName
}
}
}
# result is now either empty or a list of file fullnames
$result | set-content -Path 'C:\Users\username\Downloads\MatchedFiles.txt'

How can I replace an entire file name using a CSV index in Powershell?

When the .mp3 files are downloaded from the call recording website they are not in our desired format. I want to replace the entire filename and move the files into a new folder. The default format is:
incoming#_outgoing#_yyyymmdd_hhmmss
I have a Search and Replace CSV which uses data from the call recording website:
Replace: Telephone call reference (TelCall_yyyymmdd_Surname)
Search: Exact call time from call recording software (yyyymmdd_hhmmss)
I have written a script that searches for the exact time and date in the filename and replaces it with the desired format:
$folder = "C:\Users\R\Desktop\ClientCallsTest"
$csv = "C:\Users\R\Desktop\20180104-0224_Client_Calls_PS_CSV.csv"
$keywords = #{}
Import-Csv $csv | ForEach-Object {
$keywords[$_.Search] = $_.Replace
}
Get-ChildItem $folder -Recurse | ForEach-Object {
$newname = $_.Name
foreach ($word in $keywords.Keys) {
$newname = $newname.Replace($word, $keywords[$word])
}
if ($_.Name -ne $newname) {
Rename-Item -Path $_.FullName -NewName $newname
}
}
It indeed does rename the file, but only replaces the string in the CSV table. My desired format is just TelCall_yyyymmdd_Surname.
How would I modify this code to replace the entire file name (deleting the incoming/outgoing numbers at the beginning), rather than just the string in the lookup table? I'm sure it's a quick change but I'm stumped.
You can use the string Split() function to split your original name into parts, like:
$nameParts = "incoming#_outgoing#_yyyymmdd_hhmmss" -split "_"
will give you an array like:
PS C:\>$nameParts
incoming#
outgoing#
yyyymmdd
hhmmss
you can then assemble your now string like:
$newName = "TelCal_" + $nameParts[2] + "_" + $surename
where $surename contains your data from the csv file

Copy file to multiple locations from CSV

I am currently scanning a directory and matching file names and then copying them to various file share locations based upon csv file. The CSV file should have 2 fields: the destination column = path for the copy, and the string Find column = to identify the file to be copied.
My CSV file is like this:
"matching file names" , "Destination"
"Don" , "c:\test\a"
"Quest" , "c:\test\b"
Currently it copies all files to all the locations.
Script:
$csv = Import-Csv -Path "C:\Temp\list.csv"
$filepath = 'C:\Temp\Source'
Get-ChildItem $filepath | foreach {
$criteria = $csv
$find = $csv | select -ExpandProperty find
$a = $_.FullName
foreach ($f in $find) {
if ($a -like "*$f*") {
foreach ($c in $criteria) {
Copy-Item $_.FullName $c.Destination
}
}
}
}
For one thing there's no column "find" in your CSV. I'm going to assume that the column you titled "matching file names" actually has the title "find".
Your problem is caused by the nested double loop in your code. You iterate over each pattern, then copy all files matching a given pattern to every destination from the CSV with the innermost loop.
Use just one loop for the CSV data (to keep the association between filter string and destination path intact), and the problem will disappear. I'd also recommend using the Contains() method instead of the -like operator, so you don't need to add wildcards to the filter string.
Get-ChildItem $filepath | ForEach-Object {
foreach ($filter in $csv) {
if ($f.Name.Contains($filter.find)) {
Copy-Item $_.FullName $filter.Destination
}
}
}

Powershell - Assigning unique file names to duplicated files using list inside a .csv or .txt

I have limited experience with Powershell doing very basic tasks by itself (such as simple renaming or moving files), but I've never created one that has the need to actually extract information from inside a file and apply that data directly to a file name.
I'd like to create a script that can reference a simple .csv or text file containing a list of unique identifiers and have it assign those to a batch of duplicated files (they all have the same contents) that share a slightly different name in the form of a 3-digit number appended as the prefix of a generic name.
For example, let's say my list of files are something like this:
001_test.txt
002_test.txt
003_test.txt
004_test.txt
005_test.txt
etc.
Then my .csv contains an alphabetical list of what I would like those to become:
Alpha.txt
Beta.txt
Charlie.txt
Delta.txt
Echo.txt
etc.
I tried looking at similar examples, but I'm failing miserably trying to tailor them to get it to do the above.
EDIT: I didn't save what I already modified, but here is the baseline script I was messing with:
$file_server = Read-Host "Enter the file server IP address"
$rootFolder = 'C:\TEMP\GPO\source\5'
Get-ChildItem -LiteralPath $rootFolder -Directory |
Where-Object { $_.Name -as [System.Guid] } |
ForEach-Object {
$directory = $_.FullName
(Get-Content "$directory\gpreport.xml") |
ForEach-Object { $_ -replace "99.999.999.999", $file_server } |
Set-Content "$directory\gpreport.xml"
# ... etc
}
I think this is to replace a string inside a file though. I need to replace the file name itself using a list from another file (that is not getting renamed), while not changing the contents of the files that are being renamed.
So you want to rename similar files with those listed in a text file. Ok, here's what you are going to need for my solution (alias listed in parenthesis): Get-Content (GC), Get-ChildItem (GCI), Where (?), Rename-Item, ForEach (%)
$NewNames = GC c:\temp\Namelist.txt #Path, including file name, to list of new names
$Name = "dog.txt" #File name without the 001_ prefix
$Path = "C:\Temp" #Path to search
$i=0
GCI $path | ?{$_.Name -match "\d{3}_$Name"}|%{Rename-Item $_.FullName $NewNames[$i];$i++}
Tested as working. That gets your list of new names and saves it as an array. Then it defines your file name, path, and sets $i to 0 as a counter. Then for each file that matches your pattern it renames it based off of item number $i in the array of new names, and then increments $i up one number and moves to the next file.
I haven't tested this, but it should be pretty close. It assumes you have a CSV with a column named FileNames and that you have at least as many names in that list as there are on disk.
$newNames = Import-Csv newfilenames.csv | Select -ExpandProperty FileNames
$existingFiles = Get-ChildItem c:\someplace
for ($i = 0; $i -lt $existingFiles.count; $i++)
{
Rename-Item -Path $existingFiles[$i].FullName -NewName $newNames[$i]
}
Basically, you create two arrays and using a basic for loop steping through the list of files on disk and pull the name from the corresponding index in the newNames array.
Does your CSV file map the identifiers to the file names?
Identifier,NewName
001,Alpha
002,Beta
If so, you'll need to look up the identifier before renaming the file:
# Define the naming convention
$Suffix = '_test'
$Extension = 'txt'
# Get the files and what to rename them to
$Files = Get-ChildItem "*$Suffix.$Extension"
$Csv = Import-Csv 'Names.csv'
# Rename the files
foreach ($File in $Files) {
$NewName = ($Csv | Where-Object { $File.Name -match '^' + $_.Identifier } | Select-Object -ExpandProperty NewName)
Rename-Item $File "$NewName.$Extension"
}
If your CSV file is just a sequential list of filenames, logicaldiagram's answer is probably more along the lines of what you're looking for.

Using PowerShell, how would I create/split a file in 1-week chunks?

This is in reference to the post here: How to delete date-based lines from files using PowerShell
Using the below code (contributed by 'mjolinor') I can take a monolithic (pipe "|" delimited) CSV file and create a trimmed CSV file with only lines containing dates less than $date:
$date = '09/29/2011'
foreach ($file in gci *.csv) {
(gc $file) |
? {[datetime]$_.split('|')[1] -lt $date
} | set-content $file
}
The above code works great! What I need to do now is create additional CSV files from the monolithic CSV file with lines containing dates >= $date, and each file needs to be in 1-week chunks going forward from $date.
For example, I need the following 'trimmed' CSV files (all created from original CSV):
All dates less than 09/30/2011 (already done with code above)
File with date range 09/30 - 10/6
File with date range 10/7 - 10/14
Etc, etc, until I reach the most recent date
You can use the GetWeekOfYear method of Calendar like this
$date = (Get-Date)
$di = [Globalization.DateTimeFormatInfo]::CurrentInfo
$week = $di.Calendar.GetWeekOfYear($date, $di.CalendarWeekRule, $di.FirstDayOfWeek)
to determine the week number of a given date.
This is NOT tested with your input (it was adapted from some script I use for time-slicing and counting Windows log events), but should be close to working. It can create files on any arbitrary time span you designate in $span:
$StartString = '09/29/2011'
$inputfile = 'c:\somedir\somefile.csv'
$Span = new-timespan -days 7
$lines = #{}
$TimeBase = [DateTime]::MinValue
$StartTicks = ([datetime]$startString).Ticks
$SpanTicks = $Span.Ticks
get-content $inputfile |
foreach {
$dt = [datetime]$_.split('|')[1]
$Time_Slice = [int][math]::truncate(($dt.Ticks - $StartTicks) / $SpanTicks)
$lines[$Time_Slice] += #($_)
}
$lines.GetEnumerator() |
foreach {
$filename = ([datetime]$StartString + [TimeSpan]::FromTicks($SpanTicks * $_.Name)).tostring("yyyyMMdd") + '.csv'
$_.value | export -csv $filename
}