Using Get-ChildItem with a string variable - powershell

I'm using Get-ChildItem to save file locations that match user-input parameters. I can't seem to figure out (I'm new to powershell) how to use string variables with cmdlets.
I'll define $search_param as it is in my script below.
I've tried using the built in debugger on powershell ise, and if anything it's confused me even more.
This is where I found that replacing the use of '$search_param' with the actual value of $search_param produced a csv output, but using '$search_param' gives no output. Some useful information might be that I defined $search_param with double quotations.
The line that is causing all the issues is:
Get-ChildItem 'C:\Users\USER\' -recurse '$search_param'
$search_param is defined as:
| where { $_.name -like "*peanut*" -or $_.name -like "*butter*" -or $_.name -like "*test*"}
| where { $_.extension -in ".txt",".csv",".docx" }
| where { $_.CreationTime -ge "08/07/1998" -and $_.CreationTime -le "08/07/2019" }
| select FullName | Export-Csv -Path .\SearchResults.csv -NoTypeInformation
Funnily enough I had it all working before I went to lunch and came back to a different story..
I'm using Export-csv in another piece of my script and that is working as intended.
I may have touched something small that isn't related to the line I provided as I think this should be working..

Per Technician's comment:
You can use invoke expression to "evaluate or run a specified string as a command and return the results of the expression or command."
Invoke-Expression "Get-ChildItem 'C:\Users\SGouldin\' -recurse $search_param"
From a little bit of research, relying on invoke expression isn't always the best practice.
In my case, I needed it because of the way I was attempting to handle user input (some weeeeird string concatenation/joins).

Related

grep gci output in powershell

I am trying to determine if some environment variables are set (for postgres environment). They usually start with PG. (E.g. PGUSER, PGPASSWORD, etc). The following command does output it. (Provided I set it previously).
gci env:* | sort name | more
To eliminate the scrolling I tried the following:
gci env:* | sort name | select-string "PG"
This doesn't return anything. What am I doing wrong here?
Edit: the alternative I have for now:
gci env:* | sort name | % { $var = $_.Name + ":" + $_.Value; Write-Output $var } | select-string "PG"
There must be a better alternative.
You're using the wrong mindset. Don't try to work with PowerShell like everything is a string. That's Unix-like thinking, and it's going to work as well as driving nails with a screwdiver. You need to switch to object-oriented thinking because in PowerShell you're working with objects 99% of the time.
Generally, you would just do this for something as simple as what you're looking for:
Get-ChildItem Env:PG* | Sort-Object -Property Name
If the globbing that Get-ChildItem supports doesn't work, you would want to use Where-Object with the -like operator which is similar globbing to what Get-ChildItem can do:
Get-ChildItem Env:* | Where-Object Name -like 'PG*' | Sort-Object -Property Name
If you need to search values, you can do it like this:
Get-ChildItem Env:* | Where-Object Value -like 'PG*' | Sort-Object -Property Name
And if you want to do both, you'd use the full synax of Where-Object:
Get-ChildItem Env:* | Where-Object { $_.Name -like 'PG*' -or $_.Value -like 'PG*' } | Sort-Object -Property Name
Or you can use the -match operator, which lets you specify a .Net regular expression:
Get-ChildItem Env:* | Where-Object Name -match '^PG' | Sort-Object -Property Name
Or if you know exactly what you're looking for:
$Vars = 'PGUSER', 'PGPASSWORD'
Get-ChildItem Env:* | Where-Object Name -in $Vars | Sort-Object -Property Name
Remembering, of course, that PowerShell is usually case-insensitive. You can specify -clike, -cmatch, -cin, etc. if you want case-sensitive operators.
Alternately, you can use the $env: automatic variable namespace.
if ($null -eq $env:PGUSER) { 'Not set' }
See also Get-Help about_Environment_Variables.
Beware that setting environment variables permanently is not exactly self-evident. It's described briefly in the above link, but the bottom line is that you have to call [System.Environment]::SetEnvironmentVariable(), which you can find documented here. In Windows land, environment variables are basically legacy features with the exception of Windows OS level variables (like PATH) so they're no longer supported like you might expect.
Your approach to how this command should work and your instinct that there has to be a better alternative is exactly correct. This is quite a frustrating issue in my mind and I also asked a variation on this question a few days back.
Select-String only handles strings and what you are passing to it in the above is not a string, so it returns nothing. Obviously, you might think that since Select-String requires a string, that it would implicitly change it into a string, but no. So the next thing to consider is to change it to a string, but that creates even more confusion.
gci env:* | sort name | out-string | select-string "Pro"
So now you just get everything returned. What's happening here is that out-string returns all lines as a single string, so if there is any hit for "Pro" you get everything returned.
What you need to do is to use out-string -stream which splits the string up by linebreaks so that you get a string per line, and only then do you get rational output.
gci env:* | sort name | out-string -stream | select-string "Pro"
More on this here: Using PowerShell sls (Select-String) vs grep vs findstr. The github request linked to in there is trying to change the functionality so that select-string will implicitly have an out-string -stream in the background so that your original command will work.
Often we need strings to output results and there is nothing wrong with wanting to manipulate strings (in fact, it depends what you need of course - if you need to do further object manipulations, keep it as an object for that, but if you just need the string output, you should not have to jump through hoops to get that!). If you use a string-manipulation tool like select-string then PowerShell should at least convert the incoming information to a string to provide meaningful output. Compare with findstr: if you pipe the above to findstr.exe, exactly that will happen and it will implicitly convert with | out-string -stream for findstr (and all other external / non-PowerShell programs) and so gci env:* | findstr "Pro" (on a PowerShell console!) gives you rational output. select-string is a string-manipulation tool so I find the idea that people are not thinking right about it for expecting a string-manipulation tool to manipulate the incoming information as a string to be unfair on users. PowerShell is an incredibly versatile language but I think this is a common area of confusion. Hopefully, future versions of select-string will operate in the above fashion as a result of the change request on GitHub, but in the meantime, just use | out-string -stream and it will all work as expected, including for other string manipulations which you can then deal with easily:
(gci env:* | sort name | out-string -stream) -replace "Pro", "XXX" | select-String "XXX"
to keep this short: Your approach doesn't work in PowerShell. All you need to do is
# Short Version
gci env: | ? Name -match "PG" | sort Name
# Long Version
Get-ChildItem -Path env: |
Where-Object -FilterScript { $_.Name -match "PG" } |
Sort-Object -Property Name
Select-String works fine with string content piped one by one instead of a big stream.
Cheers

How to filter ProfileList with wildcards

I'm writing a script to help streamline the deletion of users profile list's on our Citrix servers. I cant figure out how to filter the ProfileImagePath by wildcards.
On our Windows 2008 servers in Regedit I can search and sort the profile list of users cia its Profileimagepath but this gets all the users, I cant seem to extend that filter to only bring back wildcard entries.
set-location | 'HKLM:\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList' |% {Get-ItemProperty $_.pspath } | Select
profileImagePath=username*
What I am getting is the whole list of user profiles, or if I put the filter =username* entry I get an error, What I wanted to get was just the results of the wildcard, Ideally just the usernames I put in the wildcard are what I want returned.
The code you posted is utterly broken. Please go find a PowerShell tutorial before proceeding any further.
Set-Location does not produce output, and even if it did, piping cmdlet output into a string wouldn't do anything at all.
You must not wrap statements at arbitrary places like you do in your sample code.
The wrapped registry path string will preserve the embedded newline (thus not matching what you want it to match).
Putting the argument of the Select-Object statement on the next line without escaping the newline will run Select-Object without arguments and the arguments as a separate statement (which most likely will throw an error because the arguments aren't a valid statement by themselves).
= is an assignment operator in PowerShell. Comparison operators are -eq (equality), -like (wildcard matches), and -match (regular expression matches).
You need Where-Object for filtering pipelined objects, not Select-Object. The latter is for selecting object properties.
You propbably meant to do something like this:
$username = '...'
$profilePath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
Get-ChildItem $profilePath | Where-Object {
(Get-ItemProperty $_.PSPath).ProfileImagePath -like "*${username}*"
}
or (simpler) like this:
$username = '...'
$profilePath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
Get-ChildItem $profilePath |
Get-ItemProperty |
Where-Object { $_.ProfileImagePath -like "*${username}*" }

How would I specify a directory to run a PowerShell script that would edit file extensions?

I am new to PowerShell and new to IT. I've been asked by my boss to write a PowerShell script that will identify filenames that have no file extension and then change them to .PDF files. After doing some research online I've found a script that had a similar purpose and tried to tailor it to my needs:
$proj_files = Get-ChildItem | Where-Object {$_.Extension -eq "."}
ForEach ($file in $proj_files) {
$filenew = $file.Name + ".pdf"
Rename-Item $file $filenew
}
My first question is does the logic in this script make sense? Is "Extension -eq "." the correct syntax to specify a filename with no extension? My other thought was to use Extension -eq "null" or something similar. If I do need to use a null value, what would that look like? My other question is how would I specify a given directory for this script to search through, or would I even need to? My thought here would be to specify the path under Get-ChildItem, like so: $proj_files = Get-ChildItem -Path C:\Users\mthomas\Documents | Where-Object {$_.Extension -eq ".'} Does that seem correct? I am hesitant to test this out before getting a second opinion because I don't want to change every file extension on my computer or something stupid like that. Anyhow, thanks everyone for the help.
You can do something like the following to find files in a directory without an extension, and rename them to have a PDF extension:
$directory = "C:\Path\To\Directory"
Get-ChildItem -File $directory | Where-Object { -Not $_.Extension } | Foreach-Object {
$_ | Rename-Item -NewName "$($_.Name).pdf"
}
Let's break this down
$directory = "C:\Path\To\Directory"
This is where we set the directory we want to locate files without extensions in. It doesn't have to be set as a static variable but since you are just getting your feet wet with Powershell this keeps it simple.
Get-ChildItem -File $directory
Get-ChildItem is the cmdlet which is used to list directory contents (also aliased to gci, ls, and dir). -File tells it to only list files, and $directory references the directory we want to search from, which we set above. Note that Get-ChildItem might behave differently depending on the provider (for example, you can also use Get-ChildItem on a registry key), but if you are working with a filesystem path you do not need to worry about additional providers for this case.
|
Passes the previous output down the pipeline. This is a common operator in Powershell, but basically you can string commands together using it. You can read more about the pipeline at https://learn.microsoft.com/en-us/powershell/scripting/getting-started/fundamental/understanding-the-windows-powershell-pipeline?view=powershell-6
Where-Object { -Not $_.Extension }
Where-Object evaluates a condition on one or more items, and filters out items that do not meet the condition. Since Get-ChildItem can return one or more files, we use the -Not operator in the ScriptBlock (denoted by {} and make sure that there is no extension on the file. $_, or $PSItem, is a special variable used by the pipeline, in this case $_ equals each item returned by Get-ChildItem. The Extension property exists on files returned by Get-ChildItem, and will be blank, or evaluated as $False. So filtering on -Not $_.Extension is the same as saying to only match objects that are missing a file extension. Where-Object can be read about in more detail here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/where-object?view=powershell-6
Foreach-Object { SCRIPTBLOCK }
Similar to Where-Object, but runs code for each object in the pipeline rather than evaluating and filtering out objects which don't match a condition. In this case, we pipe the each file without an extension to Rename-Item, which I'll break down further below. More information on Foreach-Object can be read about here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-6
$_ | Rename-Item -NewName "$($_.Name).pdf"
Rename the current file in the Foreach-Object block to the new name with .pdf appended. The "$( ... )" is called a sub-expression, which is a string interpolation technique that lets you run a command within a string, and make its output part of the string. You could achieve the same effect by doing $_ | Rename-Item -NewName ( $_.Name + ".pdf" ) which just adds a .pdf to the end of the current name.
Summary
The pipeline is a very powerful tool in Powershell, and is key to writing efficient and less bloated scripts. It might seem complex at first, but the more you use it the less daunting it will seem. I highly suggest reading the additional documentation I linked to above as it should help fill in any gaps I may have missed in my explanations above.
To simplify the breakdown above, the command does this, in this order: Gets all files in the specified directory, selects only the files that do not have an extension, then renames each file found without an extension to have a .pdf at the end.
The logic in the script - the overall shape - makes understandable sense, but is not right for it to work as you intend.
Testing on my computer here:
new-item -ItemType File -Name 'test'
get-item test | format-list *
get-item test | foreach { $_.extension; $_.Extension.length; $_.extension.GetType().name }
a file with no extension shows up with an empty string (blank content, length 0, type String, so your where-object { $_.Extension -eq "." } needs to be looking for "" instead of ".".
But:
Get-ChildItem | Where-Object { $_.Extension -eq '' }
shows me some folders as well, because they also have no extension in their name, so you might want Get-ChildItem -File to restrict it to just files.
how would I specify a given directory for this script to search through, or would I even need to?
It would run in the current directory, whichever shows up in your prompt PS C:\wherever> so if you need it to run somewhere else, yes you'd need to change to that folder or specify in get-childitem -LiteralPath 'c:\path\to\wherever'. You haven't mentioned subfolders, if you need those included, get-childitem -Recurse switch as well.
Speaking of subfolders, your $filenew = $file.Name + ".pdf" only makes sense in the current directory, I think it would work better if you used the full filename including path, so they definitely get renamed in the same place they were found $filenew = $file.FullName + ".pdf"
Is "Extension -eq "." the correct syntax to specify a filename with no extension?
Being careful here, what you wrote in your question was correct syntax but incorrect string content. What you've written here with quotes on the left of Extension is incorrect syntax.
My other thought was to use Extension -eq "null" or something similar. If I do need to use a null value, what would that look like?
And being careful here, "null" is not a null value, it's a string containing the four letter word 'null'.
You don't need to use a null value here, normally if you do it looks like $null, but in this case you could use where-object { [string]::IsNullOrEmpty($_.Extension) } but there's no benefit to it, I think.
And, as a stylistic choice, both "" and '' are strings, but "" can contain variables and sub-expressions, so if you have plain text it's a neat habit to use '' for it because it makes it clear to the reader that you intend there to be nothing special happening in this string.
Then your code, with parameter names given, looks more like:
$proj_files = Get-ChildItem -LiteralPath 'C:\Users\mthomas\Documents' |
Where-Object {$_.Extension -eq '.'}
foreach ($file in $proj_files)
{
$filenew = $file.FullName + '.pdf'
Rename-Item -LiteralPath $file.FullName -NewName $filenew
}
If you want to see what it will do, use -WhatIf on the end of Rename-Item:
Rename-Item -LiteralPath $file.FullName -NewName $filenew -WhatIf
Then it won't make the changes, just tell you what it would do.
I am hesitant to test this out before getting a second opinion because I don't want to change every file extension on my computer or something stupid like that
Sensible. But internet people are going to tell you to test their code before running it, because ultimately it's your responsibility to safeguard your files, rather than trust random code from the internet, so having test folders, having a spare machine, having a good backup, playing with PowerShell in pieces until you are happy with what they do, they're all good habits to get into as well.

PowerShell script file modify time>10h and return a value if nothing is found

I am trying to compose a script/one liner, which will find files which have been modified over 10 hours ago in a specific folder and if there are no files I need it to print some value or string.
Get-ChildItem -Path C:\blaa\*.* | where {$_.Lastwritetime -lt (date).addhours(-10)}) | Format-table Name,LastWriteTime -HideTableHeaders"
With that one liner I am getting the wanted result when there are files with
modify time over 10 hours, but I also need it to print value/string if there are
no results, so that I can monitor it properly.
The reason for this is to utilize the script/one liner for monitoring purposes.
Those cmdlet Get-ChildItem and where clause you have a would return null if nothing was found. You would have to account for that separately. I would also caution the use of Format-Table for output unless you are just using it for screen reading. If you wanted a "one-liner" you would could this. All PowerShell code can be a one liner if you want it to be.
$results = Get-ChildItem -Path C:\blaa\*.* | where {$_.Lastwritetime -lt (date).addhours(-10)} | Select Name,LastWriteTime; if($results){$results}else{"No files found matching criteria"}
You have an added bracket in your code, that might be a copy artifact, I had to remove. Coded properly would look like this
$results = Get-ChildItem -Path "C:\blaa\*.*" |
Where-Object {$_.Lastwritetime -lt (date).addhours(-10)} |
Select Name,LastWriteTime
if($results){
$results
}else{
"No files found matching criteria"
}

Why won't it rename the file? Powershell

Can someone tell me why this script won't work?
Get-ChildItem "\\fhnsrv01\home\aborgetti\Documentation\Stage" -Filter *.EDIPROD | `
Foreach-Object{
$content = Get-Content $_.FullName
#filter and save content to a new file
$content | Where-Object {$_ -match 'T042456'} | Rename-Item `
($_.BaseName+'_834.txt')
I found this syntax from another question on here and changed the environment variables.
For some reason it won't change the name of the file. The filename is
'AIDOCCAI.D051414.T042456.MO.EDIPROD'
Help much appreciated.
UPDATE
Thanks to TheMadTechnician I was able to get some working stuff. Great stuff actually. Figure I should share with the world!
#Call Bluezone to do file transfer
#start-process "\\fhnsrv01\home\aborgetti\Documentation\Projects\Automation\OpenBZ.bat"
#Variable Declarations
$a = Get-Date
$b = $a.ToString('MMddyy')
$source = "\\fhnsrv01\home\aborgetti\Documentation\Stage\"
$dest = "\\fhnsrv01\home\aborgetti\Documentation\Stage\orig"
#Find all the files that have EDIPROD extension and proceed to process them
#First copy the original file to the orig folder before any manipulation takes place
Copy-item $source\*.EDIPROD $dest
# Now we must rename the items that are in the table
Switch(GCI \\fhnsrv01\home\aborgetti\Documentation\Stage\*.EDIPROD){
{(GC $_|Select -first 1) -match "834*"}{$_ | Rename-Item -NewName {$_.BaseName+'_834.txt'}}
{(GC $_|Select -first 1) -match "820*"}{$_ | Rename-Item -NewName {$_.BaseName+'_820.txt'}}
}
Get-ChildItem's -Filter has issues, I really hesitate to use it in general. If it were up to me I'd do something like this:
Get-ChildItem "\\fhnsrv01\home\aborgetti\Documentation\Stage" |
?{$_.Extension -match ".EDIPROD" -and $_.name -match "T042456"}|
%{$_.MoveTo($_.FullName+"_834.txt")}
Well, I would put it all on one line, but you can line break after the pipe and it does make it a little easier to read, so there you have it. I'm rambling, sorry.
Edit: Wow, I didn't even address what was wrong with your script. Sorry, kind of distracted at the end of my work day here. So, why doesn't your script work? Here's why:
You pull a file and folder listing for the chosen path. That's great, it should work, more or less, I have almost no faith in the -Filter capabilities of the file system provider, but anyway, moving on!
You take that list and run it through a ForEach loop processing each file that matches your filter as such:
You read the contents of the file, and store them in the variable $content
You run the contents of the file, line by line, there a Where filter looking for the text "T042456"
For each line that matches that text you attempt to rename something to that line's basename plus _834.txt (the line of text is a string, it doesn't have a basename property, and it's not an object that can be renamed, so this is going to fail)
So, that's where the issue is. You're pulling the contents of the file, and parsing that line by line trying to match the text instead of matching against the file name. If you removed Everything after the first pipe up to the Where statement, and then for your rename-item put -newname before your desired name, and change the ( ) to { } that goes around the new name, and you would be set. Your code would work. So, your code, modified as I said, would look like:
Get-ChildItem "\\fhnsrv01\home\aborgetti\Documentation\Stage" -Filter *.EDIPROD |
Where-Object {$_ -match 'T042456'} | Rename-Item -NewName {$_.BaseName+'_834.txt'}
Though I have a feeling you want $.Name and not $.BaseName. Using $_.BaseName will leave you with (to use your example file name):
'AIDOCCAI.D051414.T042456.MO_834.txt`
Edit2: Really that's a whole different question, how to match multiple criteria, but the question is here, I'm here, why not just get it done?
So, you have multiple criteria for matching the file names. That really doesn't affect your loop to be honest, what it does affect is the Where statement. If you have multiple options what you probably want is a RegEx match. Totally doable! I'm only going to address the Where statement (?{ }) here, this won't change anything else in the script.
We leave the extension part, but we're going to need to modify the file name part. With RegEx you can match against alternative text by putting it in parenthesis and splitting up the various options with a pipe character. So it would look something like this:
"(T042456|T195917|T048585)"
Now we can incorporate that into the rest of the Where statement and it looks like this:
?{$_.Extension -match ".EDIPROD" -and $_.name -match "(T042456|T195917|T048585)"}
or in your script:
Where-Object {$_ -match "(T042456|T195917|T048585)"}
Edit3: Hm, need the first line for the qualifier. That complicates things a bit. Ok, so what I'm thinking is to get our directory listing, get the first line of each file with the desired extension, make an object that has two properties, the first property is the fileinfo object for the file, and the other property will be the first line of the file. Wait, I think we can do better. Switch (GCI *.EDIPROD){(get-content|select -first 1) -match 820}{Rename 820};{blah blah -match 834}{rename 834}}. Yeah, that should work. Ok, actual script, not theoretical gibberish script time. This way if you have other things to look for you can just add lines for them.
Switch(GCI \\fhnsrv01\home\aborgetti\Documentation\Stage\*.EDIPROD){
{(GC $_|Select -first 1).substring(177) -match "^834"}{$_ | Rename-Item -NewName {"834Dailyin$b"};Continue}
{(GC $_|Select -first 1).substring(177) -match "^820"}{$_ | Rename-Item -NewName {$_.BaseName+'_820.txt'};Continue}
}
Again, if you want the EDIPROD part to remain in the file name change $_.BaseName to $_.Name. Switch is pretty awesome if you're trying to match against different things and perform different actions depending on what the results are. If you aren't familiar with it you may want to go flex your google muscles and check it out.
Hm, alternatively we could have gotten the first line inside the Where filter, run a regex match against that, and renamed the file based on the regex match.
GCI \\fhnsrv01\home\aborgetti\Documentation\Stage\*.EDIPROD | ?{(GC $_ | Select -First 1) -match "(820|834)"}|Rename-Item -NewName {$_.Name+"_"+$Matches[1]+".txt"}
Then you just have to update the Where statement to include anything you're trying to match against. That's kind of sexy, though not as versatile as the switch. But for just simple search and rename it works fine.
Try it like this way
Get-ChildItem -Filter "*T042456*" -Recurse | % {Rename-Item $_ "$_ _834.txt"}