Powershell Test-Path in foreach loop null - powershell

I have a list of numbers in a file (123-45-678, 876-54-321, but they're on separate lines) and I'm trying to do something if a number is in the file and something else if it is not.
$list = Get-Content $env:USERPROFILE\Documents\list.txt
Foreach ($obj in $list)
{
$nxt = Get-ChildItem -Path $env:USERPROFILE\Documents\files -Recurse -Filter "*$obj*"
$FileExists = Test-Path $nxt
If ($FileExists -ne $True)
{
Write-Host "Yippee"
}
Else
{
Write-Host "Not found"
}
}
If there is a number in the file it seems to work but if it's not I get the following
TEST-PATH : Cannot bind argument to parameter 'Path' because it is null.
I thought the purpose of test-path was to return something if it couldn't find anything?

Why are you negating the results of both Test-Path and if([System.IO.File]::Exists($nxt)) ?
If either returns $true, then the file EXISTS.
Because you say you want to do something if a file with any such number in its name is found and something else if not, I would do it using the regular expression -match operator.
For that, you need to put all possible numbers from the text file in a single string, separated by the regex OR operator |:
# get the numbers from the file and for security remove empty or whitespace-only lines
$list = Get-Content -Path "$env:USERPROFILE\Documents\list.txt" | Where-Object { $_ -match '\S' }
# build a regex string from that array
$regex = ($list | ForEach-Object { [regex]::Escape($_) }) -join '|'
# get all files and test if they match any of the numbers
Get-ChildItem -Path "$env:USERPROFILE\Documents\files" -Recurse -File | ForEach-Object {
if ( $_.Name -match $regex ) {
Write-Host "File '$($_.Name)' exists" -ForegroundColor Green
}
else {
Write-Host "File '$($_.FullName)' does not match any of the items in the list."
}
}
Result would look something like this:
File '876-54-321.txt' exists
File 'C:\snowman\Documents\files\somefile.txt' does not match any of the items in the list.
File 'Whatever 123-45-678-901.txt' exists
File 'XYZ 876-54-321.txt' exists

Related

select-string with multiple conditions with powershell

I'm looking for a way to find 2 different lines in a file and only if those 2 line exist I need to perform a task.
So far this is my code
$folderPath = c:\test
$files = Get-ChildItem $Folderpath -Filter *.txt
$find = 'stringA'
$find2 = 'StringB'
$replace = 'something to replace with string b'
if ($files.Length -gt 0 ) {
$files |
select -ExpandProperty fullname |
foreach {
If(Select-String -Path $_ -pattern $find , $find2 -quiet )
{
(Get-Content $_) |
ForEach-Object {$_ -replace $find2, $replace } |
Set-Content $_
write-host "File Changed : " $_
}
}
}
else {
write-host "no files changed"
}
Currently if I run it once it will change the files but if I run it again it will also notify me that it changed the same files instead of the output "no files changed"
Is there a simpler way to make it happen?
Thanks
The Select-String cmdlet selects lines matching any of the patterns supplied to it. This means that the following file contains a match:
PS> Get-Content file.txt
This file contains only stringA
PS> Select-String -Pattern 'stringA', 'stringB' -Path file.txt
file.txt:1:This file contains only stringA
Passing the -Quiet flag to Select-String will produce a boolean result instead of a list of matches. The result is $True even though only one of the patterns is present.
PS> Get-Content file.txt
This file contains only stringA
PS> Select-String -Pattern 'stringA', 'stringB' -Path file.txt -Quiet
True
In your case, Select-String chooses all the files containing either 'stringA' or 'stringB', then replaces all instances of 'stringB' in those files. (Note that replacements are also performed in files you did not want to alter)
Even after the replacements, files containing only 'stringA' still exist: these files are caught and reported by your script the second time you run it.
One solution is to have two separate conditions joined by the -and operator:
If (
(Select-String -Path $_ -Pattern 'stringA' -Quiet) -and
(Select-String -Path $_ -Pattern 'stringB' -Quiet)
)
After this the script should work as intended, except that it won't report "no files changed" correctly.
If you fix your indentation you'll realise that the final else clause actually checks if there are no .txt files in the folder:
$files = Get-ChildItem $Folderpath -Filter *.txt
...
if ($files.length -gt 0) {
...
} else {
# will only display when there are no text files in the folder!
Write-Host "no files changed"
}
The way to resolve this would be to have a separate counter variable that increments every time you find a match. Then at the end, check if this counter is 0 and call Write-Host accordingly.
$counter = 0
...
foreach {
if ((Select-String ...) ...) {
...
$counter += 1
}
}
if ($counter -eq 0) {
Write-Host "no files changed"
}
To complement equatorialsnowfall's helpful answer, which explains the problem with your approach well, with a streamlined, potentially more efficient solution:
$folderPath = c:\test
$searchStrings = 'stringA', 'stringB'
$replace = 'something to replace string B with'
$countModified = 0
Get-ChildItem $Folderpath -Filter *.txt | ForEach-Object {
if (
(
($_ | Select-String -Pattern $searchStrings).Pattern | Select-Object -Unique
).Count -eq $searchStrings.Count
) {
($_ | Get-Content -Raw) -replace $searchStrings[1], $replace |
Set-Content $_.FullName
++$countModified
write-host "File Changed: " $_
}
}
if ($countModified -eq 0) {
Write-Host "no files changed"
}
A single Select-String call is used to determine if all pattern match (the solution scales to any number of patterns):
Each Microsoft.PowerShell.Commands.MatchInfo output object has a .Pattern property that indicates which of the patterns passed to -Pattern matched on a given line.
If, after removing duplicates with Select-Object -Unique, the number of patterns associated with matching lines is the same as the number of input patterns, you can assume that all input patterns matched (at least once).
Reading each matching file as a whole with Get-Content's -Raw switch and therefore performing only a single -replace operation per file is much faster than line-by-line processing.

powershell check for existence of multiple inputs

I need to check if files from input exists.
I split multiple inputs for example BWMDL.VML BWMDL.STA etc and write out files that are already in folder
I check if files from input are present in folder or not.
But I'm getting True, even if the file doesnt exists, also the output from test-path is printed twice, with different result.
Set-Variable -Name Files -Value (Read-Host "instert file name")
Set-Variable -Name FromPath -Value ("C:\Users\Desktop\AP\AP\parser\*.VML" , "C:\Users\Desktop\AP\AP\parser\*.STA")
Set-Variable -Name NameOfFiles (Get-ChildItem -Path $FromPath "-Include *.VML, *.STA" -Name)
Write-Host "FILES IN FOLDER:"
$NameOfFiles
Write-host "---------------------"
Write-host "FILES FROM INPUT: "
Splitted
Write-host "---------------------"
Write-host "FILE EXISTS: "
ForEach ($i in Splitted) {
FileToCheck
}
function Splitted {
$Files -Split " "
}
function FileToCheck {
Test-Path $FromPath -Filter $Files -PathType Leaf
}
For example I'm getting like this result
You are over complicating this.
Once you get the names of all files with extension .VML or .STA in an array, you do not have to use Test-Path anymore, since you know the files in array $NameOfFiles actually do exist, otherwise Get-ChildItem would not have listed them.
This means you can get rid of the helper functions you have defined, which BTW should have been written on top of your code, so before calling on them.
Try
$Files = (Read-Host "instert file name(s) separated by space characters" ) -split '\s+'
$FromPath = 'C:\Users\Desktop\AP\AP\parser'
# if you need to recurse through possible subfolders
$NameOfFiles = (Get-ChildItem -Path $FromPath -Include '*.VML', '*.STA' -File -Recurse).Name
# without recursion (so if files are directly in the FromPath):
# $NameOfFiles = (Get-ChildItem -Path $FromPath -File | Where-Object {$_.Extension -match '\.(VML|STA)'}).Name
Write-Host "FILES IN FOLDER:"
$NameOfFiles
Write-host "---------------------"
Write-host "FILES FROM INPUT: "
$Files
Write-host "---------------------"
Write-host "FILE EXISTS: "
foreach ($file in $Files) { ($NameOfFiles -contains $file) }
Output should look like
instert file name(s) separated by space characters: BWMDL.VML BWMDL.STA
FILES IN FOLDER:
BWMDL.STA
BWMDL.VML
---------------------
FILES FROM INPUT:
BWMDL.VML
BWMDL.STA
---------------------
FILE EXISTS:
True
True

Loop through folders/files and replace word

Trying to loop through a folders/subfolders that contains multiple html files to find a specific string/word.
Once the powershell script finds it - it will list which files contains the matching words.
Maybe a prompt for the user to replace the word or just set a variable such as $replaceword = 'test2'
$match = "THIS IS A TEST"
#$replacement = Read-Host "Please enter a solution name"
$files = Get-ChildItem -path C:\Users\testfolder -filter *THIS IS A TEST* -Recurse
$files
Sort-Object -Descending -Property { $_.FullName }
#Rename-Item -newname { $_.name -replace $match, $replacement } -force
$files = Get-ChildItem -path C:\Users\testfolder -include *.html -Recurse
foreach($file in $files)
{
((Get-Content $file.fullname) -creplace $match)
read-host -prompt "Done! Press any key to close."
}
You can do something like the following, which will replace matching content of .html files in the C:\Users\testfolder directory structure:
$matchString = 'THIS IS A TEST'
$replaceString = 'New String'
$files = Get-ChildItem -Path 'C:\Users\testfolder' -filter *.html -Recurse
foreach ($file in $files) {
$matchFound = $false
$output = switch -regex -file $file.fullname {
$matchString { $_ -replace $matchString,$replaceString
$matchFound = $true
}
default { $_ }
}
if ($matchFound) {
$output | Set-Content $file.fullname
}
}
If you really want an interactive prompt to replace the string, you can move the $replaceString to a different place within the script logic and add the Read-Host command.
Explanation:
The top of the script contains $matchString (the case-sensitive string you want to match) and $replaceString (the replacement string).
Get-ChildItem uses -Filter to find all files that end with the HTML extension. The -Recurse switch is used to search all subdirectories from the path.
The switch statement is used in favor of Get-Content because it typically performs faster. We only want to overwrite a file when a match is found. $matchFound begins as $false and will become $true when a match is found.
-creplace is used here since your source code used it. -creplace performs a case-sensitive regex match and replace.
Interactive Alternative:
For an interactive element, you can ask the user to enter the replacement string each time a match is found. This simply moves the $replaceString assignment inside of the switch statement and lists the file that contains the match.
$matchString = 'THIS IS A TEST'
$files = Get-ChildItem -Path 'C:\Users\testfolder' -filter *.html -Recurse
foreach ($file in $files) {
$matchFound = $false
$output = switch -regex -file $file.fullname {
$matchString { $replaceString = Read-Host -Prompt "$($file.fullname) contains '$matchString'. Enter your replacement string"
$_ -replace $matchString,$replaceString
$matchFound = $true
}
default { $_ }
}
if ($matchFound) {
$output | Set-Content $file.fullname
}
}
What about something like this, it's a simpler example:
$files = Get-ChildItem -Path c:\path\here -Recurse
$matchString = 'THIS IS A TEST'
$replacementString = 'replace'
foreach ($i in $files){
#Check its a file
if (!$i.PSIsContainer){
#Check the name contains the $matchString
if ($i.Name -match $matchString){
Set-Content -Path $i.FullName -Value $replacementString -Confirm:$true
}
}
}
Let me know if I've misunderstood what you want :)

Matching Lines in a text file based on values in CSV

Hi Everyone,
I am having trouble with the below script. Here is the requirement:
1) Each text file needs to be compared with a single CSV file. The CSV file contains the data to that if present in the text file should match.
2) If the data in the text file matches, output the matches only and run jobs etc..
3) If the text file has no matches to the CSV file, exit with 0 as no matches are found.
I have tried to do this, but what I end up with is matches, and also non matches. What I really need is to match the lines, run the jobs,exit, if text file has no matches, then return 0
$CSVFIL = Import-Csv -Path $DRIVE\test\csvfile.csv
$TEXTFIL = Get-Content -Path "$TEXTFILFOL\*.txt" |
Select-String -Pattern 'PAT1' |
Select-String -Pattern 'PAT2' |
Select-String -Pattern 'TEST'
ForEach ($line in $CSVFIL) {
If ($TEXTFIL -match $line.COL1) {
Write-Host 'RUNNING:' ($line.JOB01)
} else {
write-host "No Matches Found Exiting"
I would handle this a different way. First you need to find matches, if there are matches then process else output 0.
$matches = #()
foreach ($line in $CSVFIL)
{
if ($TEXTFIL -contains $line.COL1)
{ $matches += $line }
}
if ($matches.Count -gt 0)
{
$matches | Foreach-Object {
Write-Output "Running: $($_.JOB01)"
}
}
else
{
Write-Output "No matches found, exiting"
}
$CSVFIL = Import-Csv -Path "$DRIVE\test\csvfile.csv"
Get-Content -Path "$TEXTFILFOL\*.txt" |
where {$_ -like "*PAT1*" -and $_ -like "*PAT2*" -and $_ -like "*TEST*" } |
%{
$TEXTFOUNDED=$_; $CSVFIL | where {$TEXTFOUNDED -match $_.COL1} |
%{ [pscustomobject]#{Job=$_.JOB01;TextFounded=$TEXTFOUNDED;Col=$_.COL1 } }
}

Identify Empty Folders

I would like to identify a specific empty folder in our user profiles.
I have a text file containing all of our user names that I want the script to refer to. The script will loop each user directory and either output to file or screen and say if the directory is empty. Hidden files do not have to count!
Something similar
FOR /F %U IN (C:\UserList\UserList.TXT) DO *Find and List Empty Folder* \\Server\Share\%U\Target_Folder
Powershell solutions welcome!
This article on Technet provides the following Powershell code snippet to identify all empty folders:
$a = Get-ChildItem C:\Scripts -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName
Replace "C:\Scripts" with the root folder you want to search.
Update:
The following script will get you in the ballpark.
$content = Get-Content C:\Temp\FolderList.txt
foreach ($line in $content)
{
Write-Host $line -NoNewline
$testObject = Test-Path -Path $line
if ($testObject)
{
$folder = Get-Item -Path $line
$filesCount = $folder.GetFiles().Count
if ($filesCount.Equals(0))
{
Write-Host " - Empty folder"
}
else
{
Write-Host " - Contains files"
}
}
else
{
Write-Host " - Invalid path"
}
}