In the code below I can use the -and operator to make a compound if statement, but when using Test-Path with -and there is a syntax error.
What is the proper way to use -and with a command like Test-Path?
$a = 1
$b = 1
$c = 1
if ($a -eq $b -and $a -eq $c ) {
write-host "Variables are equal."
}
$path1 = "C:\Windows"
$path2 = "C:\Users"
if (Test-Path $path1 -and Test-Path $path2) {
write-host "paths exist."
}
If you put brackets around your usages of Test-Path then it works, i.e.
$path1 = "C:\Windows"
$path2 = "C:\Users"
if ((Test-Path $path1) -and (Test-Path $path2)) {
write-host "paths exist."
}
DeanOC's helpful answer provides an effective solution.
As for why the parentheses (brackets) are needed:
PowerShell has two fundamental parsing modes:
argument mode, which works like traditional shells
expression mode, which works like traditional programming languages.
Running Get-help about_Parsing provides an introduction to these modes.
Test-Path $path1 and Test-Path $path2 in isolation are parsed in argument mode.
Operator -and can only be used in expression mode.
In order to use the output of the argument-mode Test-Path commands in expression mode, they must be enclosed in (...):
Use of (...) forces a new parsing context, and
the 1st token of a given parsing context determines whether it is parsed in argument or expression mode.
Another thing to consider is PowerShell's pervasive support for collections, which often allows you to operate at a higher level of abstraction:
$paths = "C:\Windows", "C:\Users" # define *array* of input paths
if ((Test-Path $paths) -notcontains $False) { # See if ALL paths exist.
"ALL paths exist."
}
Test-Path accepts an array of paths to test, and outputs a corresponding array of Booleans indicating the respective existence.
Operator -notcontains tests non-membership in the LHS array; in other words: if no Boolean returned by Test-Path is $False, the implication is that all input paths exist.
Note that Write-Host was deliberately omitted, because in order to send something to PowerShell's success stream (the analog to stdout in traditional shells), you don't need an explicit output command at all, and, conversely, Write-Host actually bypasses the success stream - see this blog post.
Related
I'm trying to prepend a PatientOrderNumber to the name of files in a directory if the file name includes a unique ID from my CSV file. I've tried the -like and -contains parameters and have not been successful.
here is my code:
$csvPath = "C:\Users\dougadmin28\Desktop\Test\1bs4e.csv"
## Variable to hold path of Files that need to be renamed ##
$filePath = "C:\Users\dougadmin28\Desktop\Node Modify File Name App\pdfs"
$csv = Import-Csv $csvPath #| Select-Object -Property PatientOrderNumber, FileID, DocumentID, LastName, FirstName|Export-Csv 'C:\Users\dougadmin28\Desktop\Node Modify File Name App\documents.csv'
$files = Get-ChildItem $filePath
foreach ($item in $csv){
foreach($file in $files) {
if($file.FullName -contains '*$item.DocumentID*') {
Rename-Item $file.FullName -NewName "$($item.PatientOrderNumber+"_"+($item.FileID)+'_'+($item.DocumentID)+'_'+($item.LastName)+'_'+($item.FirstName)+($file.extension))"
}#End forEach
}#End if
} #End forEach```
tl;dr
Instead of:
$file.FullName -contains '*$item.DocumentID*' # WRONG
use:
$file.FullName -like "*$($item.DocumentID)*"
Note the use of " instead of ' and the $(...) around the property-access expression.
As for what you tried:
$file.FullName -contains '*$item.DocumentID*'
-contains tests array membership of the RHS in the array-valued LHS, it doesn't perform substring matching.
See this answer for the difference between PowerShell's -contains operator and the String.Contains() .NET method.
'...' strings are taken literally (verbatim), so no expansion (interpolation) is performed; "..." (expandable strings) must be used for string interpolation.
See about_Quoting_Rules for documentation about PowerShell's string-literal types.
Inside an expandable string, only simple variable references (e.g., $item) can be embedded as-is; anything more complex, such as an expression that involves property access (e.g. $item.DocumentID), requires enclosing in $(...), the subexpression operator.
See this answer for an overview of string expansion (interpolation) in PowerShell.
Basically I want to do a check if a directory exists then run this section, if not exit.
The script I have is:
$Path = Test-Path c:\temp\First
if ($Path -eq "False")
{
Write-Host "notthere" -ForegroundColor Yellow
}
elseif ($Path -eq "true")
{
Write-Host " what the smokes"
}
But it returns nothing.
The error comes from the fact that the return value of Test-Path is a Boolean type.
Hence, don't compare it to strings representation of Boolean but rather to the actual $false/$true values. Like so,
$Path = Test-Path c:\temp\First
if ($Path -eq $false)
{
Write-Host "notthere" -ForegroundColor Yellow
}
elseif ($Path -eq $true)
{
Write-Host " what the smokes"
}
Also, note that here you could use an else statement here.
Alternatively, you could use the syntax proposed in #user9569124 answer,
$Path = Test-Path c:\temp\First
if (!$Path)
{
Write-Host "notthere" -ForegroundColor Yellow
}
elseif ($Path)
{
Write-Host " what the smokes"
}
In a comparison operation PowerShell automatically converts the second operand to the type of the first operand. Since you're comparing a boolean value to a string, the string will be cast to a boolean value. Empty strings will be cast to $false and non-empty strings will be cast to $true. Jeffrey Snover wrote an article "Boolean Values and Operators" about these automatic conversions that you can check for further details.
As a result this behavior has the (seemingly paradox) effect that each of your comparisons will evaluate to the value of your variable:
PS C:\> $false -eq 'False'
False
PS C:\> $false -eq 'True'
False
PS C:\> $true -eq 'False'
True
PS C:\> $true -eq 'True'
True
Essentially that means that if your Test-Path statements evaluates to $false neither of your conditions will match.
As others have pointed out you can fix the issue by comparing your variable to actual boolean values, or by just using the variable by itself (since it already contains a boolean value that can be evaluated directly). However, you need to be careful with the latter approach. In this case it won't make a difference, but in other situations automatic conversion of different values to the same boolean value might not be the desired behavior. For instance, $null, 0, empty string and empty array are all interpreted as a boolean value $false, but can have quite different semantics depending on the logic in your code.
Also, there is no need to store the result of Test-Path in a variable first. You can put the expression directly into the condition. And since there are only two possible values (a file/folder either exists or doesn't exist), there is no need to compare twice, so your code could be reduced to something like this:
if (Test-Path 'C:\temp\First') {
Write-Host 'what the smokes'
} else {
Write-Host 'notthere' -ForegroundColor Yellow
}
If I'm not mistaken, one can simple say:
if($Path)
OR
if(!$Path)
But I might be wrong as I can't test atm.
Additionally there is the Test-Path cmdlet available. Unfortunately I cannot describe the difference or suggest the most suitable method without knowing the case and scenario.
[EDITED TO CLARIFY ANSWER]
$Path = "C:\"
if($Path)
{
write-host "The path or file exists"
}
else
{
write-host "The path or file isn't there silly bear!"
}
Hope that adds clarity. With this method, no cmdlets needed. The returned boolean is interpreted for you automatically and runs code blocks if it meets the criteria of the test, in this case if the path C:\ exists. This would be true of files in longer file paths, C:\...\...\...\...\file.txt
To make some things clear, always use Test-Path (or Test-Path with Leaf to check for a file).
Examples I've tested:
$File = "c:\path\file.exe"
$IsPath = Test-Path -Path $File -PathType Leaf
# using -Not or ! to check if a file doesn't exist
if (-Not(Test-Path -Path $File -PathType Leaf)) {
Write-Host "1 Not Found!"
}
if (!(Test-Path -Path $File -PathType Leaf)) {
Write-Host "2 Not Found!"
}
# using -Not or ! to check if a file doesn't exist with the result of Test-Path on a file
If (!$IsPath) {
Write-Host "3 Not Found!"
}
If (-Not $IsPath) {
Write-Host "4 Not Found!"
}
# $null checks must be to the left, why not keep same for all?
If ($true -eq $IsPath) {
Write-Host "1 Found!"
}
# Checking if true shorthand method
If ($IsPath) {
Write-Host "2 Found!"
}
if (Test-Path -Path $File -PathType Leaf) {
Write-Host "3 Found!"
}
1. Code Description alias how it is intended to work
User enters a path to a directory in PowerShell. Code checks if any folder within the declared directory contains no data at all. If so, the path of any empty folder will be shown on the prompt to the user and eventually removed from the system.
2. The Issue alias what I am struggling with
The code I just wrote doesn't count the depth of a folder hierarchy as I would expect (the column in the output table is blank). Besides that, the program works okay - I've still got to fix the issue where my code removes empty parent directories first and child directories later, which of course will cause an error in PowerShell; for instance, take
C:\Users\JohnMiller\Desktop\Homework
where Homework consists of Homework\Math\School Project and Homework\Computer Science\PowerShell Code. Note that all directories are supposed to be empty with the exception of PowerShell Code, the folder containing this script. (Side note: A folder is considered empty when no file dwells inside. At least that's what my code is based on for now.)
3. The Code
# Delete all empty (sub)folders in [$path]
[Console]::WriteLine("`n>> Start script for deleting all empty (sub)folders.")
$path = Read-Host -prompt ">> Specify a path"
if (test-path $path)
{
$allFolders = Get-ChildItem $path -recurse | Where {$_.PSisContainer -eq $True}
$allEmptyFolders = $allFolders | Where-Object {$_.GetFiles().Count -eq 0}
$allEmptyFolders | Select-Object FullName,#{Name = "FolderDepth"; Expression = {$_.DirectoryName.Split('\').Count}} | Sort-Object -descending FolderDepth,FullName
[Console]::WriteLine("`n>> Do you want do remove all these directories? Validate with [True] or [False].") #'#
$answer = Read-Host -prompt ">> Answer"
if ([System.Convert]::ToBoolean($answer) -eq $True)
{
$allEmptyFolders | Remove-Item -force -recurse
}
else
{
[Console]::WriteLine(">> Termination confirmed.`n")
exit
}
}
else
{
[Console]::WriteLine(">> ERROR: [$($path)] is an invalid directory. Program terminates.`n")
exit
}
The depth-count problem:
Your code references a .DirectoryName property in the calculated property passed to Select-Object, but the [System.IO.DirectoryInfo] instances output by Get-ChildItem have no such property. Use the .FullName property instead:
$allEmptyFolders |
Select-Object FullName,#{Name='FolderDepth'; Expression={$_.FullName.Split('\').Count}} |
Sort-Object -descending FolderDepth,FullName
Eliminating nested empty subfolders:
To recap your problem with a simple example:
If c:\foo is empty (no files) but has empty subdir. c:\foo\bar, your code outputs them both, and if you then delete c:\foo first, deleting c:\foo\bar next fails (because deleting c:\foo also removed c:\foo\bar).
If you eliminate all nested empty subdirs. up front, you not only declutter what you present to the user, but you can then safely iterative of the output and delete one by one.
With your approach you'd need a 2nd step to eliminate the nested empty dirs., but here's a depth-first recursive function that omits nested empty folders. To make it behave the same way as your code with respect to hidden files, pass -Force.
function Get-RecursivelyEmptyDirectories {
[cmdletbinding()]
param(
[string] $LiteralPath = '.',
[switch] $Force,
[switch] $DoNotValidatePath
)
$ErrorActionPreference = 'Stop'
if (-not $DoNotValidatePath) {
$dir = Get-Item -LiteralPath $LiteralPath
if (-not $dir.PSIsContainer) { Throw "Not a directory path: $LiteralPath" }
$LiteralPath = $dir.FullName
}
$haveFiles = [bool] (Get-ChildItem -LiteralPath $LiteralPath -File -Force:$Force | Select-Object -First 1)
$emptyChildDirCount = 0
$emptySubdirs = $null
if ($childDirs = Get-ChildItem -LiteralPath $LiteralPath -Directory -Force:$Force) {
$emptySubDirs = New-Object System.Collections.ArrayList
foreach($childDir in $childDirs) {
if ($childDir.LinkType -eq 'SymbolicLink') {
Write-Verbose "Ignoring symlink: $LiteralPath"
} else {
Write-Verbose "About to recurse on $($childDir.FullName)..."
try { # If .AddRange() fails due to exceeding the array list's capacity, we must fail too.
$emptySubDirs.AddRange(#(Get-RecursivelyEmptyDirectories -DoNotValidatePath -LiteralPath $childDir.FullName -Force:$Force))
} catch {
Throw
}
# If the last entry added is the child dir. at hand, that child dir.
# is by definition itself empty.
if ($emptySubDirs[-1] -eq $childDir.FullName) { ++$emptyChildDirCount }
}
} # foreach ($childDir ...
} # if ($childDirs = ...)
if (-not $haveFiles -and $emptyChildDirCount -eq $childDirs.Count) {
# There are no child files and all child dirs., if any, are themselves
# empty, so we only output the input path at hand, as the highest
# directory in this subtree that is empty (save for empty descendants).
$LiteralPath
} else {
# This directory is not itself empty, so output the (highest-level)
# descendants that are empty.
$emptySubDirs
}
}
Tips regarding your code:
Get-ChildItem -Directory is available in PSv3+, which is not only shorter but also more efficient than Get-ChildItem | .. Where { $_.PSisContainer -eq $True }.
Use Write-Host instead of [Console]::WriteLine
[System.Convert]::ToBoolean($answer) only works with the culture-invariant string literals 'True' and 'False' ([bool]::TrueString and [bool]::FalseString, although case variations and leading and trailing whitespace are allowed).
Would you like to explain what is happing in the PowerShell code at the bottom of this post?
I got my first, lets say "hello world" in PowerShell, and it needed these lines of code. It works like a charm, but I am not sure what it does, exactly.
The questions starts at
$( ,$_; Get-Content $Path -ea SilentlyContinue) | Out-File $Path
So this is what I understand so far.
We create a function called Insert-Content. With the params (input that will be interpeted as a string and will be added to $path).
function Insert-Content {
param ( [String]$Path )
This is what the function does/processes:
process {
$( ,$_;
I am not sure what this does, but I guess it gets "the input" (the "Hello World" before the | in "Hello World" | Insert-Content test.txt).
And then we got -ea SilentylyContinue, but what does it do?
process {
$( ,$_; Get-Content $Path -ea SilentlyContinue) | Out-File $Path
It would be greatly appreciated if you could explain these two parts
$( ,$_;
-ea SilentylyContinue
Code needed/used: Add a string to the first line of a doc.
function Insert-Content {
param ( [String]$Path )
process {
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
}
}
"Hello World" | Insert-Content test.txt
process {...} is used for applying the code inside the scriptblock (the {...}) to each parameter argument that the function reads from a pipeline.
$_ is an automatic variable containing the current object. The comma operator , preceding the $_ converts the string value to a string array with a single element. It's not required, though. The code would work just as well with just $_ instead of ,$_.
Get-Content $Path reads the content of the file $Path and echoes it as to the success output stream as an array of strings (each line as a separate string).
The ; separates the two statements ,$_ and Get-Content $Path from each other.
| Out-File $Path writes the output back to the file $Path.
The subexpression operator $() is required to decouple reading the file from writing to it. You can't write to a file when a process is already reading from it, so the subexpression ensures that reading is completed before writing starts.
Basically this whole construct
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
echoes the input from the pipeline (i.e. the "Hello World") followed by the current content of the file $Path (effectively prepending the input string to the file content) and writes everything back to the file.
The -ea SilentlyContinue (or -ErrorAction SilentlyContinue) suppresses the error that would be thrown when $Path doesn't already exist.
The relevant section of code must be handled as a whole:
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
First, as others have said, -ea is the shortened version of -ErrorAction. -ErrorAction SilentlyContinue tells the cmdlet "Suppress any error messages and continue executing." See Get-Help about_Common_Parameters -ShowWindow.
Next, the $() is the sub-expression operator. It means "Evaluate what is between the parentheses as its own command and return the result(s)." See Get-Help about_Operators -ShowWindow.
This subexpression here is:
,$_;Get-Content $Path -ea SilentlyContinue
It contains two statements: ,$_ and Get-Content $Path -ea SilentlyContinue. The semicolon is just the end of statement identifier to separate the two.
,$_; is two kind of complex parts.
$_ is the special pipeline variable. It always contains whatever object is in the current pipeline. See Get-Help about_Automatic_Variables -ShowWindow for more about $_ (it's mostly used with ForEach-Object and Where-Object cmdlets, so check those out, too), and Get-Help about_pipelines -ShowWindow for more help with pipelines.
The comma here is the comma operator (see Get-Help about_Operators -ShowWindow again). It creates an array from the objects on either side. For example, 1,2,3 creates an array with three elements 1, 2, and 3. If you want a two item array, you can say 1,2.
What if you want a one item array? Well, you can't say 1, because Powershell will think you forgot something. Instead, you can say ,1.
You can test it with the -is operator:
PS C:\> 1,2,3 -is [Array]
True
PS C:\> 1 -is [Array]
False
PS C:\> ,1 -is [Array]
True
Why might you want to create a one item array? Well, if later on your code is assuming the item is an array, it can be useful. In early editions of Powershell, properties like .Count would be missing for single items.
For completeness, yes, I believe you could write:
$( #($_);Get-Content $Path -ea SilentlyContinue)
And I think you could rewrite this function:
function Insert-Content {
param ( [String]$Path )
process {
#Read from pipeline
$strings = #($_);
#Add content of specified file to the same array
$strings += Get-Content $Path -ea SilentlyContinue;
#Write the whole array to the file specified at $Path
$strings | Out-File $Path;
}
}
So this adds content from the pipeline to the start of a file specified by -Path.
It's also somewhat poor practice not to create a parameter for the pipeline object itself and define it. See... well, see all the topics under Get-Help "about_Functions*", but mostly the Advanced ones. This is an advanced topic.
I've been trying to get an IF-ELSE clause to work within my little powershell v2 script, and I think I'm having some problems with my parsing. Here's the code I have currently:
$dir = test-path C:\Perflogs\TestFolder
IF($dir -eq "False")
{
New-Item C:\Perflogs\TestFolder -type directory
get-counter -counter $p -Continuous | Export-Counter C:\PerfLogs\TestFolder\Client_log.csv -Force -FileFormat CSV -Circular -MaxSize $1GBInBytes
}
Else
{
get-counter -counter $p -Continuous | Export-Counter C:\PerfLogs\TestFolder\Client_log.csv -Force -FileFormat CSV -Circular -MaxSize $1GBInBytes
}
So basically I want it to establish the $dir variable as testing to see if the path I want exists. If it doesn't, it should create that folder and run the counters. If it does, it should not create the folder but should still run counters.
I've got $p defined elsewhere, and the get-counters statement works fine. Right now, whether the folder exists or not I'm getting an error about new-item not working.
Am I using the wrong operator for -eq after doing that test?
You should have:
if ($dir -eq $false)
because the string "False" is not equal to the boolean value $false.
Try changing this:
IF($dir -eq "False")
to this:
IF($dir -eq $false)
x0n already answered, so I wont repeat that, but I noticed another "Gotcha" you should be aware of.
Your code is trying to test for the existence of a directory "TestFolder", however you test-path command is not restricted to checking only for directories. Meaning, if you actually happen to have a file by the same name "TestFolder" it will still return true.
To be more careful, you should add the "-PathType Container" switch so that it will fail if there is a file by that name and only pass if there is a directory by that name.
$dir = test-path C:\Perflogs\TestFolder -PathType Container
In addition to the other answers about $false, I've had syntax issues with v3. Specifically
if($dir -eq $false)
{
didn't work.
if($dir -eq $false){
did work. YMMV, but you've been warned.
It might work
If($? -ne 0)
if the given condition is false, which is defined in earlier variables(This is not for the above question)
If($dir -contains 'False')