PowerShell: Is there a way to search an specific word in a file's name and open the file if it finds it? - powershell

I haven't been able to figure out how to make this task that sounds simple in PowerShell.
I am trying to make a powershell variable that represents a file only using a part of it's name, since the rest of the name changes periodically. This should represent a little better what is my intention.
#Each day the number changes except for the Name part.
Name1, Name2, Name3...
#Variable must be able to work regardless of the number it has since the Name part never changes.
$Variable: Volume\Folder\Name(X).exe
I'm sorry if i'm not explaining myself well enough.
I'll provide any aditional information that is needed.

Well, to me it seems to be two diiferent tasks at hand:
First your title suggests You are lokking for a way to check the filenames of files in a given directory i assume and run that file with the default filehandler (again i can only speculate)
# 1. task
$path="C:\myfolder\"
$mySearchKey="taxes"
$myItmes=Get-ChildItem -Path $myPath
foreach($item in $myItems){
if($item.name -like "*$mySearchkey*"){
$matchingPath=$path+$item.name
Invoke-Item $matchingPath
}
}
Secondly In your description and the code example the question seems to be evolving around the idea to create dynamic variables for filenames most likely for the files we where opening before, based on the day in relation to a start date:
#2. task
$afileName="Marry"
$startdate= Get-Date "2022-12-06"
$todays= get-date
$numberOfDays = New-TimeSpan -Start $startdate -End $todays
$numberOfDays++ # since a differnce of 0 days means your in the 1. day
$newPath="Volume\Folder\"+$afileName+$numberOfDays+".exe"
But I yet have to figure out your end-game. How are the two coming together?

Run script where you want to search.
$all_files = ls
$filename = Read-Host "Enter File Name"
foreach ($item in $filename)
{
if ($item -match $filename)
{
Get-Content $item
}
}

Related

Powershell - Text file is not written to

Good evening,
I'm hardly experienced in programming, but every now and then I try to build the things I need myself.
What do I want to achieve with the script?
This script should read a text file with words.
There is one new word per line. When reading the script should look if the word has between 3 and 16 letters. If it has less than 3 or more than 16, then the word is skipped. But if it is between the 3 and 16, then the word will be saved in a new Text_File. Again, I would love a new word every line.
Here is what I created.
Please don't judge my script too hard.
$regex = '[A-Z][a-z]{3,16}'
foreach ($line in Get-Content -Path C:\\Users\\Administrator\\Desktop\\namecheck\\words.txt)
{
if($line -match $regex)
{
Out-File -FilePath C:\\Users\\Administrator\\Desktop\\namecheck\\sorted.txt -Force
}
}
As mentioned above, the words are not written to a file. However, the file is created and the script also takes a little while to finish. So from my point of view something seems to happen.
'[A-Z][a-z]{3,16}' is only accounting for words that are of length 3+. That would be your first issue, and your second one is your export. Out-File isn't being told what you want to export. So, either provide it the a value via pipeline input, or using -InputObject:
$path = 'C:\Users\Administrator\Desktop\namecheck\'
$stream = [System.IO.StreamReader]::new("$path\words.txt")
while ($line = $stream.ReadLine())
{
if ($line.Length -gt 3 -and $line.Length -lt 16)
{
Out-File -FilePath "$path\sorted.txt" -InputObject $line -Append -Force
}
}
$stream.Close()
$stream.Dispose()
Although, a foreach loop is the fastest of the loops, when using Get-Content it still has to wait for the completion of the content gathering before it can move through the list. Only mentioning this since you said that the script takes quite a bit and without knowing the size of your words.txt file, im left to assume that's the cause.
With that said, using [System.IO.StreamReader] should speed up that reading of the file as you'll get to read and iterate through the file at the same time; with a while loop that is.

How do I copy a list of files and rename them in a PowerShell Loop

We are copying a long list of files from their different directories into a single location (same server). Once there, I need to rename them.
I was able to move the files until I found out that there are duplicates in the list of file names to move (and rename). It would not allow me to copy the file multiple times into the same destination.
Here is the list of file names after the move:
"10.csv",
"11.csv",
"12.csv",
"13.csv",
"14.csv",
"15.csv",
"16.csv",
"17.csv",
"18.csv",
"19.csv",
"20.csv",
"Invoices_Export(16) - Copy.csv" (this one's name should be "Zebra.csv")
I wrote a couple of foreach loops, but it is not working exactly correctly.
The script moves the files just fine. It is the rename that is not working the way I want. The first file does not rename; the other files rename. However, they leave the moved file in place too.
This script requires a csv that has 3 columns:
Path of the file, including the file name (eg. c:\temp\smefile.txt)
Destination of the file, including the file name (eg. c:\temp\smefile.txt)
New name of the file. Just the name and extention.
# Variables
$Path = (import-csv C:\temp\Test-CSV.csv).Path
$Dest = (import-csv C:\temp\Test-CSV.csv).Destination
$NN = (import-csv C:\temp\Test-CSV.csv).NewName
#Script
foreach ($D in $Dest) {
$i -eq 0
Foreach ($P in $Path) {
Copy-Item $P -destination C:\Temp\TestDestination -force
}
rename-item -path "$D" -newname $NN[$i] -force
$i += 1
}
There were no error per se, just not the outcome that I expected.
Welcome to Stack Overflow!
There are a couple ways to approach the duplicate names situation:
Check if the file exists already in the destination with Test-Path. If it does, start a while loop that appends a number to the end of the name and check if that exists. Increment the number you append after each check with Test-Path. Keep looping until Test-Path comes back $false and then break out of the loop.
Write an error message and skip that row in the CSV.
I'm going to show a refactored version of your script with approach #2 above:
$csv = Import-Csv 'C:\temp\Test-CSV.csv'
foreach ($row in $csv)
{
$fullDestinationPath = Join-Path -Path $row.Destination -ChildPath $row.NewName
if (Test-Path $fullDestinationPath)
{
Write-Error ("The path '$fullDestinationPath' already exists. " +
"Skipping row for $($row.Path).")
continue
}
# You may also want to check if $row.Path exists before attempting to copy it
Copy-Item -Path $row.Path -Destination $fullDestinationPath
}
Now that your question is answered, here are some thoughts for improving your code:
Avoid using acronyms and abbreviations in identifiers (variable names, function names, etc.) when possible. Remember that code is written for humans and someone else has to be able to understand your code; make everything as obvious as possible. Someone else will have to read your code eventually, even if it's Future-You™!
Don't Repeat Yourself (called the "DRY" principle). As Lee_daily mentioned in the comments, you don't need to import the CSV file three times. Import it once into a variable and then use the variable to access the properties.
Try to be consistent. PowerShell is case-insensitive, but you should pick a style and stick to it (i.e. ForEach or foreach, Rename-Item or rename-item, etc.). I would recommend PascalCase as PowerShell cmdlets are all in PascalCase.
Wrap literal paths in single quotes (or double quotes if you need string interpolation). Paths can have spaces in them and without quotes, PowerShell interprets a space as you are passing another argument.
$i -eq 0 is not an assignment statement, it is a boolean expression. When you run $i -eq 0, PowerShell will return $true or $false because you are asking it if the value stored in $i is 0. To assign the value 0 to $i, you need to write it like this: $i = 0.
There's nothing wrong with $i += 1, but it could be shortened to $i++, if you want to.
When you can, try to check for common issues that may come up with your code. Always think about what can go wrong. "If I copy a file, what can go wrong? Does the source file or folder exist? Is the name pulled from the CSV a valid path name or does it contain characters that are invalid in a path (like :)?" This is called defensive programming and it will save you so so many headaches. As with anything in life, be careful not to go overboard. Only check for likely scenarios; rare edge-cases should just raise errors.
Write some decent logs so you can see what happened at runtime. PowerShell provides a pair of great cmdlets called Start-Transcript and Stop-Transcript. These cmdlets log all the output that was sent to the PowerShell console window, in addition to some system information like the version of PowerShell installed on the machine. Very handy!

How to download multiple files with powershell

Okay, so, here is what I ended up editing from my original answer. Kudos to #Matt for pointing out that I should be more descriptive with my answer and explain clearly what my edits were so that other users might be able to benefit from my answer in the futre. Users like #Matt are a great part of this community and put emphasis on keeping the standards high here.
The first thing I edited/added is the ability to delete the previous log from each run. Since this script will be scheduled it is important to remove the previous logs in order to prevent utilizing up too much disk space. This can be noted under the comment: "delete log files from prev run"
# delete log files from prev Run
Remove-Item C:\alerts\logs\*.*
The next thing I edited/added is the ability to switch between host names. I did this to prevent the overwriting of the files. You can see this under the comment "change filename in order to prevent overwriting of log file". I accomplished this by checking the index of "$url" in the foreach loop, and checked to see if it was at the position where I needed to change the host name. I suspect there was a much more intuitive way to do this and I would just love it if someone chimed in with a better way to do this as its driving me crazy that I don't know a better way. It should be noted that there are a total of 44 urls where I'm downloading from, hence the magic numbers (11, 22, 33) where I change the host name. Again, if you know a better way please don't hesitate to let me know.
If ($urls.IndexOf($url) -eq 11){
$currentDir = "goxsd1704"
}
ElseIf ($urls.IndexOf($url) -eq 22){
$currentDir = "goxsd1705"
}
ElseIf ($urls.IndexOf($url) -eq 33){
$currentDir = "goxsd1706"
}
The next thing I edited/added, thanks to #Matt for the recommendation is the try catch blocks which are clearly noted in the code. I should of had these to start with as by not having them before I was assuming that the script was always going to work. Rookie mistake and point taken.With that being said, these are all my edits. The code is working fine, but improvement is always possible. Thank you for your time and answers.
# set date
$date = Get-Date -UFormat "%Y-%m-%d-%H_EST"
# delete log files from prev Run
Remove-Item C:\alerts\logs\*.*
# setup download links
$urls = "http://subdomain.domain.com:portnumber/LogA/API_DBG_CS_Logs/dbg_a.$date.log"
function DownloadFMOSLogs()
{
try
{
# assign working dir to currentDir
$currentDir = "goxsd1703"
# instantiate web-client.
$wc = New-Object System.Net.WebClient
# loop through each url
foreach ($url in $urls)
{
# change filename to prevent overwriting of log file
If ($urls.IndexOf($url) -eq 11){
$currentDir = "goxsd1704"
}
ElseIf ($urls.IndexOf($url) -eq 22){
$currentDir = "goxsd1705"
}
ElseIf ($urls.IndexOf($url) -eq 33){
$currentDir = "goxsd1706"
}
# get file name
$fileName = $url.SubString($url.LastIndexOf('/')+1)
# create target file name
$targetFileName = "C:\alerts\logs\" + $currentDir + "_" + $fileName
$wc.DownloadFile($url, $targetFileName)
Write-Host "Downloaded $url to file location $targetFileName"
}
} catch [System.Net.WebException],[System.IO.IOException]
{
"An error occurred. Files were not downloaded."
}
}
DownloadFMOSLogs
Write-Host ""
Write-Host "Download of application log files has successfully completed!"
Invoke-WebRequest is a good way in Powershell to download files and the OutFile parameter will put this straight to disk, docs are here.
Have a go with Invoke-WebRequest -Uri $link -OutFile $targetFileName
You have a couple of problems and an issue or two.
$urls is not an array like you think it is. It is actually one whole string. Try something like this instead:
$urls = "http://subdomain.domain.com:port/LogA/API_DBG_CS_Logs/dbg_a.$date.log",
"http://subdomain.domain.com:port/LogA/API_DBG_CS_Logs/dbg_b.$date.log"
The variable will expand in that string just fine. The issue before is that you were concatenating the string starting from the first part because of the order of operations. When you add an array to a string on the left hand side the array gets converted to a space delimited string. Have a look at a smaller example which is exactly what you tried to do.
"hello" + 2,2 + "there"
You could have made what you had work if you wrapped each one in a set of brackets first.
("hello" + 2),(2 + "there")
This code might make sense elsewhere but as others have pointed out you have a useless loop about lines in a file. foreach($line in Get-Content .\hosts.txt). If you don't use it get rid of it.
You don't really use $targetDir to its full potential. If you are going to use the working directory of the script at least use some absolute paths. Side note the comments don't really match what is happening which is likely related to 2. above
# preprend host to file name to keep file names diff.
$targetFilePath = [io.path]::combine($pwd,"test.txt")
# download the files.
$wc.DownloadFile($link, $targetFilePath)
You should try and make that unique somehow since the files will overwrite eachother as you have it coded.
I would also wrap that in a try block in case the download fails and you can report properly on that. As of now you assume it will work every time.

How to parse file name in powerShell

This is the name of my file myregistration_20180105041258_NOTIFICATION_1.zip and 20180105041258 the numbers are a timestamp. I've so many files of this format. These files will be posted to my share path every day. I've automated to download all the files. But I want to download daily files with help of date. Can anyone suggest me how can I do this using power shell???
If I have got this right, then your requirement is to change the numbers(in the file names) which are actually a timestamp, into a datetime format and the use this to download the files or do whatever operation you deem to.
For that, I would use the -split parameter to get the number from the filename and then convert the number into datetime format using the PoSh ParseExact function. The code will look something like this.
$string = " myregistration_20180105041258_NOTIFICATION_1.zip"
$array = #($string.Split('_'))
$datetime = [datetime]::parseexact($array[1], 'yyyyMMddhhmmss', $null)
Now your $datetime variable will contain the date of the corresponding file and you can use this to proceed further. If you have a bunch of files, you can loop through each of them using a foreach loop.
For example:
$original = "myregistration_20180105041258_NOTIFICATION_1.zip";
$trimmed = $original | Select-String -Pattern "myregistration" -InputObject {$_.TrimEnd("whatever you want to trim")}
P.S. It's possible also if you need to get the timestamp only to say:
$original -match "\d" and pull the value of it from $Matches[0].

Create variable from CSV

I want to make variables from a particular column in a CSV.
CSV will have the following headers:
FolderName,FolderManager,RoleGroup,ManagerEmail
Under FolderName will be a list of rows with respective folder names such as: Accounts,HR,Projects, etc... (each of these names is a separate row in the FolderName column)
So I would like to create a list of variables to call on in a later stage. They would be something like the following:
$Accounts,
$HR,
$Projects,
I have done a few different scripts based on searching here and google, but unable to produce the desired results. I am hoping someone can lead me in the right direction here to create this script.
Versions of this question ("dynamic variables" or "variable variables" or "create variables at runtime") come up a lot, and in almost all cases they are not the right answer.
This is often asked by people who don't know a better way to approach their problem, but there is a better way: collections. Arrays, lists, hashtables, etc.
Here's the problem: You want to read a username and print it out. You can't write Hello Alice because you don't know what their name is to put in your code. That's why variables exist:
$name = Read-Host "Enter your name"
Write-Host "Hello $name"
Great, you can write $name in your source code, something which never changes. And it references their name, which does change. But that's OK.
But you're stuck - how can you have two people's names, if all you have is $name? How can you make many variables like $name2, $name3? How can you make $Alice, $Bob?
And you can...
New-Variable -Name (Read-Host "Enter your name") -Value (Read-Host "Enter your name again")
Write-Host "Hello
wait
What do you put there to write their name? You're straight back to the original problem that variables were meant to solve. You had a fixed thing to put in your source code, which allowed you to work with a changing value.
and now you have a varying thing that you can't use in your source code because you don't know what it is again.
It's worthless.
And the fix is that one variable with a fixed name can reference multiple values in a collection.
Arrays (Get-Help about_Arrays):
$names = #()
do {
$name = Read-Host "Enter your name"
if ($name -ne '')
{
$names += $name
}
} while ($name -ne '')
# $names is now a list, as many items long as it needs to be. And you still
# work with it by one name.
foreach ($name in $names)
{
Write-Host "Hello $name"
}
# or
$names.Count
or
$names | foreach { $_ }
And more collections, like
Hashtables (Get-Help about_Hash_Tables): key -> value pairs. Let's pair each file in a folder with its size:
$FileSizes = #{} # empty hashtable. (aka Dictionary)
Get-ChildItem *.txt | ForEach {
$FileSizes[$_.BaseName] = $_.Length
}
# It doesn't matter how many files there are, the code is just one block
# $FileSizes now looks like
#{
'readme' = 1024;
'test' = 20;
'WarAndPeace' = 1048576;
}
# You can list them with
$FileSizes.Keys
and
foreach ($file in $FileSizes.Keys)
{
$size = $FileSizes[$file]
Write-Host "$file has size $size"
}
No need for a dynamic variable for each file, or each filename. One fixed name, a variable which works for any number of values. All you need to do is "add however many there are" and "process however many there are" without explicitly caring how many there are.
And you never need to ask "now I've created variable names for all my things ... how do I find them?" because you find these values in the collection you put them in. By listing all of them, by searching from the start until you find one, by filtering them, by using -match and -in and -contains.
And yes, New-Variable and Get-Variable have their uses, and if you know about collections and want to use them, maybe you do have a use for them.
But I submit that a lot of people on StackOverflow ask this question solely because they don't yet know about collections.
Dynamic variables in Powershell
Incrementing a Dynamic Variable in Powershell
Dynamic variable and value assignment in powershell
Dynamically use variable in PowerShell
How to create and populate an array in Powershell based on a dynamic variable?
And many more, in Python too:
https://stackoverflow.com/a/5036775/478656
How can you dynamically create variables via a while loop?
Basically you want to create folders based on the values you are getting from CSV File.
(FileName has headers such as FolderName,
FolderManager,
RoleGroup,
ManagerEmail)
$File=Import-csv "FileName"
$Path="C:\Sample"
foreach ($item in $File){
$FolderName=$item.FolderName
$NewPath=$Path+"\$FolderName"
if(!(Test-Path $NewPath))
{
New-Item $NewPath -ItemType Directory
}
}
Hope this HElps.
In PowerShell, you can import a CSV file and get back custom objects. Below code snippet shows how to import a CSV to generate objects from it and then dot reference the properties on each object in a pipeline to create the new variables (your specific use case here).
PS>cat .\dummy.csv
"foldername","FolderManager","RoleGroup"
"Accounts","UserA","ManagerA"
"HR","UserB","ManagerB"
PS>$objectsFromCSV = Import-CSV -Path .\dummy.csv
PS>$objectsFromCSV | Foreach-Object -Process {New-Variable -Name $PSItem.FolderName }
PS>Get-Variable -name Accounts
Name Value
---- -----
Accounts
PS>Get-Variable -name HR
Name Value
---- -----
HR
`