Why is $MyInvocation.ScriptName not set and string comparisons? - powershell

This began as an effort to have all four (4) of the PowerShell profile scripts identify themselves and their location.
$MyInvocation.ScriptName was suggested for getting the script name, but I have yet to see it be anything other than an empty string. No, not $null, empty (''). This seems counter to many suggestions here on SO.
My first assumption was that $MyInvocation.ScriptName was $null, but that is not the case. To my surprise however, it is considered to be -lt 0. What is the rationale for comparing a String to an Int32?
I did find $MyInvocation.InvocationName which appears to give the script name, but not a directory path to it.
PS C:\Users\lit\Documents\PowerShell> Get-Content .\profile.ps1
Write-Host "Current User, All Hosts # $(Split-Path -Parent $MyInvocation.MyCommand.Definition)\$(Split-Path -Leaf $MyInvocation.MyCommand.Definition)"
Write-Host "`$MyInvocation.ScriptName is $MyInvocation.ScriptName"
Write-Host "`$MyInvocation.ScriptName -eq `$null results in $($MyInvocation.ScriptName -eq $null)"
Write-Host "`$($MyInvocation.ScriptName -eq 0) results in $($MyInvocation.ScriptName -eq 0)"
Write-Host "`$($MyInvocation.ScriptName -lt 0) results in $($MyInvocation.ScriptName -lt 0)"
Write-Host "`$($MyInvocation.ScriptName -eq '') results in $($MyInvocation.ScriptName -eq '')"
Try { '' -eq 0 }
Catch { Write-Host "Caught -lt 0" }
Write-Host $MyInvocation.InvocationName
PS C:\Users\lit\Documents\PowerShell> .\profile.ps1
Current User, All Hosts # C:\Users\lit\Documents\PowerShell\profile.ps1
$MyInvocation.ScriptName is System.Management.Automation.InvocationInfo.ScriptName
$MyInvocation.ScriptName -eq $null results in False
$(System.Management.Automation.InvocationInfo.ScriptName -eq 0) results in False
$(System.Management.Automation.InvocationInfo.ScriptName -lt 0) results in True
$(System.Management.Automation.InvocationInfo.ScriptName -eq '') results in True
False
.\profile.ps1
Suggestion from JosefZ works.
PS C:\src\my-powershell> type .\gname.ps1
Write-Host "`$MyInvocation.InvocationName is $((Resolve-Path $MyInvocation.InvocationName).Path)"
Write-Host (Resolve-Path $MyInvocation.InvocationName).Path
PS C:\src\my-powershell> .\gname.ps1
$MyInvocation.InvocationName is C:\src\my-powershell\gname.ps1
C:\src\my-powershell\gname.ps1
I have tried to collect these possible answers, but still only one or two deliver what is needed. $MyInvocation.Command is always $null and $MyInvocation.ScriptName is always and empty string ('').
PS C:\src\t> Get-Content callme.ps1
"=== These two give the full path and invocation path"
Write-Host "$(Split-Path -Parent $MyInvocation.MyCommand.Definition)\$(Split-Path -Leaf $MyInvocation.MyCommand.Definition)"
Write-Host "`$(`$MyInvocation.InvocationName) is $($MyInvocation.InvocationName)"
"=== `$MyInvocation.Command is `$null, but it, and its Source member, do have a type"
Write-Host "`$MyInvocation.Command -eq `$null is $($MyInvocation.Command -eq $null)"
Write-Host "`$MyInvocation.Command is $MyInvocation.Command"
Write-Host "`$(`$MyInvocation.Command) is $($MyInvocation.Command)"
Write-Host "`$MyInvocation.Command.Source is $MyInvocation.Command.Source"
Write-Host "`$(`$MyInvocation.Command.Source) is $($MyInvocation.Command.Source)"
"==="
Write-Host "`$MyInvocation.ScriptName -eq `$null is $($MyInvocation.ScriptName -eq $null)"
Write-Host "`$MyInvocation.ScriptName -eq '' is $($MyInvocation.ScriptName -eq '')"
Write-Host "`$MyInvocation.ScriptName is $MyInvocation.ScriptName"
Write-Host "`$(`$MyInvocation.ScriptName) is $($MyInvocation.ScriptName)"
PS C:\src\t> .\callme.ps1
=== These two give the full path and invocation path
C:\src\t\callme.ps1
$($MyInvocation.InvocationName) is .\callme.ps1
=== $MyInvocation.Command is $null, but it, and its Source member, do have a type
$MyInvocation.Command -eq $null is True
$MyInvocation.Command is System.Management.Automation.InvocationInfo.Command
$($MyInvocation.Command) is
$MyInvocation.Command.Source is System.Management.Automation.InvocationInfo.Command.Source
$($MyInvocation.Command.Source) is
===
$MyInvocation.ScriptName -eq $null is False
$MyInvocation.ScriptName -eq '' is True
$MyInvocation.ScriptName is System.Management.Automation.InvocationInfo.ScriptName
$($MyInvocation.ScriptName) is

Firstly, you have a bug on line 2 of your script. You probably want to wrap $MyInvocation.ScriptName in $(...):
Write-Host "`$MyInvocation.ScriptName is $($MyInvocation.ScriptName)"
Next, the try{}catch{} will only work if the expression in the try{} block throws an exception. A simple comparison doesn't do that. If you were expecting PowerShell to complain that you are comparing try different types then... it doesn't. See below.
The comment from #PetSerAl is pertinent. When you run the profile from the PowerShell prompt, the ScriptName is what calls the current command. If you were to call your profile script from another script, you'd see the calling script name. I think you might want to look at the $MyInvocation.MyCommand property instead, as per Matthias' suggestion.
And finally...
When you compare a string with a number in the way you are doing, PowerShell "protects" you and automatically treats the second entity (the number) as a string and you're left with a string comparison: is the empty string "" less than the string "0"? Why, yes it is. It is also less than "-9999":
[PS]> "" -lt -9999
True
So -9999 is bigger than nothing? Lets switch them around to check, and we see the same principle at work:
[PS]> -9999 -gt ""
False
Seemingly a contradiction unless you remember that in each case the element on the right-hand-side of the comparison is being treated as a string or int depending on what the left-hand-side is.

Related

How to check parameter is null in where clause in powershell

I'm trying to write a PowerShell command but stucked on where clause. What i want to achieve is if parameters are not defined, where clause needs to ignore them. I tried this code but couldn't success.
I have parameters;
Param(
[parameter(position=0)]
[String]
$JobName,
[parameter(position=1)]
[String]
$JobID
)
And where clause which i tried and failed,
$Timerjob = Get-SPTimerJob | where { ($_.Id -eq $JobID) -or ($_.Title -eq $JobName) }
If $JobName or $JobID is null (or both of them), where clause should ignore them
how can i achieve this without writing multiple if clause?
Continuing from my comment:
To retrieve all Timer Jobs if both $JobName and $JobID are empty, you will need to add that condition to the Where-Object cmdlet:
$Timerjob = Get-SPTimerJob |Where-Object {
(!$JobName -and !$JobID) -or ($_.Id -eq $JobID) -or ($_.Title -eq $JobName)
}
This means if both $JobName and $JobID are empty, the condition (!$JobName -and !$JobID) is $True. Any condition with the -or comparison operator won't be able to change that (to $false) causing the whole condition to be true in that matter and all Timer Jobs returned.
In case you would like to make a difference between an empty string filter and a parameter that isn't supplied, you would probably want to do something like this:
$Timerjob = Get-SPTimerJob |Where-Object {
($PSBoundParameters.ContainsKey('JobName') -and $PSBoundParameters.ContainsKey('JobID')) -or
($_.Id -eq $JobID) -or ($_.Title -eq $JobName)
}
In this case you would e.g. still be able to retrieve Time Jobs with an empty title (-JobName ''), if even possible.

How to find a specific text is available in .txt file using powershell

I want to write a PowerShell script where I will give two string values as parameters, It should check the .txt file and should tell whether the strings are available or not in the given file. For example, if I have a list of employees details. I will give the emp_id and emp_name as input. If the name and id exist in that .txt file it should print that. If not it should print the else statement.
Function Empdetails {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory=$true)]$empid,
[Parameter(Mandatory=$true)]$empname)
$path = Get-Content C:\empdetails.txt | Where-Object {$_ -like '*name*'}
if ($path -eq $true) {
Write-Host "Found"
}
else {
Write-Host "Not Found"
}
}
I tried the above code, But it is working. Could you please help me to figure it out?
You have two parameters but you are not using them in your function, since it's not clear which parameter should be used for the file path and which for the word you're searching for in the file, I have changed the parameter names for something more explanatory.
Also note, the result of below expression will be either an array of strings, a single string or $null:
$path = ... | Where-Object {$_ -like '*name*'}
Hence, your if condition if ($path -eq $true) can never be met unless $path has assigned the literal string True. If, however, you change the order of the condition to $true -eq $path, then the condition can be met and will be $true as long as $path is not $null / empty string.
$content = 'something'
$content -eq $true # => False
$true -eq $content # => True
$content = 'True'
$content -eq $true # => True
$true -eq $content # => True
From equality operators:
The equality operator can compare objects of different types. It is important to understand that the value is on the right-hand side of the comparison can be converted to the type of the left-hand side value for comparison.
Function Empdetails {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory=$true)]$FilePath,
[Parameter(Mandatory=$true)]$WordToSearch
)
$content = Get-Content $FilePath | Where-Object {$_ -like "*$wordToSearch*"}
if ($content) {
# if `$content` is populated, use return to end the function here
return Write-Host "Found"
}
Write-Host "Not Found"
}
Empdetails -FilePath ./path/to/file.ext -WordToSearch somekeyword

If statement on Path variable - powershell - true/false test

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!"
}

Declaring variable in IF statement

Why won't this work?
if (([datetime] $a = Date),$a.DayOfWeek -ne 'Wednesday'){
Write-Host 1
Exit
}ElseIf ($a.hour -ne 9){
Write-Host 2
Exit
}
Do stuff...
but this will
if (($connected = Test-Connection 1.1.1.1 -Quiet), $connected -eq $false){
Start-Process msg -ArgumentList "$env:USERNAME Not Connected..."
Exit
}
Do stuff..
In the first example my script always exits unless I move [datetime] $a = date outside if statement. However, the second example works without issue.
Also, if insert $a in the first if statement it returns the correct datetime.
Don't use , inside an if statement, as it results in the creation of a 2-item array - an object that if() will always evaluate to $true.
The reason for this, is that when you enclose an assignment operation in plaint parentheses (that is: (), not $() which a the subexpression operator), the right-hand side of the assignment is itself returned:
PS C:\> $null = 123 # the operation itself doesn't generate output
PS C:\> ($null = 123) # but we can force it to do so
123
So when you do something like:
($a = Get-Date),$a -eq (Get-Date)
you're really producing the following expression: #($a,$a) -eq (Get-Date)
You can do it like this instead:
if(([datetime]$a = Date).DayOfWeek -ne 'Wednesday'){
# It's not wednesday
}
and
if (($connected = Test-Connection 1.1.1.1 -Quiet) -eq $false){
# No response
}
I ultimately used the following code:
if ($([datetime] $a = Date; $a.DayOfWeek -ne 'Wednesday') -eq $true){
Write-Host 1
Exit
}ElseIf ($a.hour -ne 9){
Write-Host 2
Exit
}
Do stuff..
and
if ($($connected = Test-Connection 1.1.1.1 -Quiet; $connected) -eq $false){
Write-Host 3
Exit
}
Do stuff..

IF Statement not working in Powershell

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')