Powershell if statement - powershell

In one of my servers we have an program that we need to update each month, this program is running on a terminal server.
My basic script is working (it's very simple):
Get-SmbOpenFile |where {$_.Path -eq "D:\Shares\Programs\test.exe"} |select ClientUserName, path |ft -autosize
pause
But i'am trying to make it more "smarter" so I've tried to use the IF statement:
First test:
$open = Get-SmbOpenFile |where {$_.Path -eq "D:\Shares\Programs\test.exe"} |`
select ClientUserName, path |ft -autosize
if ($open -eq "true")
{ write-host "showing open files"
}
elseif ($open -eq "false")
{ "All cloesd"
}
pause
Second test:
$open = Get-SmbOpenFile |where {$_.Path -eq "D:\Shares\Programs\test.exe"} |`
select ClientUserName, path |ft -autosize
if ($open -eq $true)
{
write-host "showing open files"
}
elseif ($open -eq $false)
{
"All cloesd"
}
I've also tried to define the variable in that way:
$open = Get-SmbOpenFile |where {$_.Path -eq "D:\Shares\Programs\test.exe"}
I'am not getting actually any output at all when i use the IF statement.
Thanks a lot for your help !

Only ever use Format-* cmdlets such as ft (Format-Table) for display formatting; never use them if data must be programmatically processed.
Format-* cmdlets output formatting instructions, not data - see this answer.
Even with | ft -autosize removed, you shouldn't compare $open to $true or $false, because such an explicit comparison will not generally work as intended if the LHS isn't already a Boolean (type [bool])[1]; instead, take advantage of PowerShell's implicit to-Boolean conversion - see the bottom section of this answer.
Your if branch doesn't actually output $open; it only outputs a Write-Host status message to the display.
To put it all together:
$open = Get-SmbOpenFile |
Where-Object {$_.Path -eq "D:\Shares\Programs\test.exe"} |
Select-Object ClientUserName, Path
if ($open) {
Write-Host "showing open files"
$open # output the result
}
else {
Write-Host "all closed"
}
Select-Object returns either:
a [pscustomobject] instance[2] (a custom object with properties .ClientUserName and .Path)
Any [pscustomobject] instance - regardless of its structure - evaluates to $true in a Boolean context.
or "nothing" (technically, [System.Management.Automation.Internal.AutomationNull]::Value), if the Where-Object cmdlet didn't find the specified path in Get-SmbOpenFile's output.
"Nothing" evaluates to $false in a Boolean context.
Again, see the bottom section of this answer for the complete set of rules of implicit to-Boolean conversion.
[1] Notably, a non-primitive object as the LHS always yields $false when compared to $true (though not with the operands reversed); e.g., (Get-Item /) -eq $true; also, any nonzero number that isn't exactly 1 will indicate $false; e.g.: 2 -eq $true. Additionally, with an array-valued LHS, -eq acts as filter, returns the subarray of matching items (e.g., (1, 2, 1) -eq $true returns 1, 1.
[2] In general, Select-Object can return multiple objects, in which case $open would receive an[object[]]-typed array of [pscustomobject] instances. An array with 2 or more elements is always $true in a Boolean context.

Related

How to Skip Null Objects returned from CimInstance

Get-CimInstance -ComputerName $pc -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $compare } | Remove-CimInstance
Because some system profiles have empty values for LocalPath, I get the following:
You cannot call a method on a null-valued expression.
At line:52 char:88
Where-Object { $_.LocalPath.split('')[-1] -eq $compare })
Any way either first check for empty value and skip to next?
ty
In PowerShell (Core) 7.1 and above, you can use the null-conditional operator, ?.:
Get-CimInstance -ComputerName $pc -Class Win32_UserProfile |
Where-Object { $_.LocalPath?.split('\')[-1] -eq $compare } |
Remove-CimInstance
As a general caveat:
While ?. works as expected with properties, with variables it unfortunately and unexpectedly requires the variable name to be enclosed in {...}, because - surprisingly - ? is a legal character in a variable name.
For instance, Set-StrictMode -Version 2; $foo = $null; ${foo}?.ToUpper() works, but using $foo?.ToUpper() instead does not, because PowerShell looks for a variable named $foo? (sic), which doesn't exist. Requesting that this counterintuitive behavior be changed - even though it is technically a breaking change - is the subject of GitHub issue #14025.
In Windows PowerShell you can use PowerShell's implicit to-Boolean conversion rules:
Get-CimInstance -ComputerName $pc -Class Win32_UserProfile |
Where-Object { $_.LocalPath -and $_.LocalPath.split('\')[-1] -eq $compare } |
Remove-CimInstance
Due to use of $_.LocalPath as an operand with the -and operator, it is implicitly coerced to a Boolean, and $null is coerced to $false; similarly, an empty string would yield $false - see the bottom section of this answer for a summary of PowerShell's to-Boolean conversion rules.
Since -and short-circuits (as does -or), the RHS expression is never evaluated if $_.LocalPath yields $false, thereby avoiding the error.
Note that -eq has higher precedence than -and (and -or), so no parentheses are needed around the RHS expression - see the conceptual about_Operator_Precedence help topic

Retrieving an empty attribute from a users account in AD using "Get-ADUser" returns null - but not null?

I'm running into trouble trying to automate a task on our Domain Controllers using a PowerShell script.
To make a long story short: I wrote a PowerShell script that retrieves the contents of specific attribute in a users account in Active Directory, and performs an action depending on result of some comparisons. It uses the "Get-ADUser" and "Select-Object" cmdlet to retrieve a value, then a "Switch" statement to evaluate and act on some comparisons. However, if no value is retrieved by the 'query', then the switch statement fails to work entirely.
First, I initialize a variable named "$CurrentADValue" and assign it the output of the the Get-ADUser command.
$CurrentADValue = Get-ADUser "first.last" -Properties * | select-object -ExpandProperty altSecurityIdentities
A switch statement then compares the contents of the "$CurrentADValue" variable, and performs an action depending on the results of the comparisons.
Switch ($CurrentADValue) {
$null {Write-OutPut "Doing X." ; Break}
{$CurrentADValue -ne $Expected_Value} {Write-OutPut "Doing X."}
$Expected_Value {Write-OutPut "Doing Y."}
}
My intention was if the field is empty ($null) or an unexpected value, I'd like to do X. If it's an expected value, I'd like to do Y. No biggie.
I ran the script against a user that I know has an empty "altSecurityIdentities" field - Lo and behold, nothing happened.
I figured the $CurrentADValue contained something other than null, so I added a quick comparison after the variable initialization to confirm it wasn't (or wasn't) null:
PS C:\Users\Me> #script.ps1
$CurrentADValue = Get-ADUser "first.last" -Properties * | select-object -ExpandProperty altSecurityIdentities
$CurrentADValue -eq $null
True
PS C:\Users\Me>
That threw me for a loop, as my Switch statement should evaluate to True in it's first comparison if it really is $null, but that just wasn't happening!
So, I pulled up PowerShell ISE and wrote a small script to confirm I wasn't crazy:
PS C:\Windows\system32> $CurrentADValue = Get-ADUser "first.last" -Properties * | select-object -ExpandProperty altSecurityIdentities
Write-OutPut "CurrentADValue is:""$CurrentADValue""`n"
Write-Output '$CurrentADValue -eq $null ='($CurrentADValue -eq $null)
Write-Output "Switch output (Below):"
Switch ($CurrentADValue) {
$null {Write-OutPut "`nCurrentADValue is Null 1"}
{$CurrentADValue -eq $null} {Write-OutPut "`nCurrentADValue is Null 2"}
{$CurrentADValue -eq ""} {Write-Output "`nThere is nothing in CurrentADValue"}
{$CurrentADValue -ne $null} {Write-OutPut "`nCurrentADValue is NOT Null"}
}
The output of which is:
CurrentADValue is:""
$CurrentADValue -eq $null =
True
Switch output (Below):
PS C:\Windows\system32>
After some random fuzzing, I added this to the 4th line: If ($CurrentADValue -eq $null) {$CurrentADValue = $null} and ran it again:
PS C:\Windows\system32> $CurrentADValue = Get-ADUser "first.last" -Properties * | select-object -ExpandProperty altSecurityIdentities
Write-OutPut "CurrentADValue is:""$CurrentADValue""`n"
Write-Output '$CurrentADValue -eq $null ='($CurrentADValue -eq $null)
If ($CurrentADValue -eq $null) {$CurrentADValue = $null}
Write-Output "Switch output (Below):"
Switch ($CurrentADValue) {
$null {Write-OutPut "`nCurrentADValue is Null 1"}
{$CurrentADValue -eq $null} {Write-OutPut "`nCurrentADValue is Null 2"}
{$CurrentADValue -eq ""} {Write-Output "`nThere is nothing in CurrentADValue"}
{$CurrentADValue -ne $null} {Write-OutPut "`nCurrentADValue is NOT Null"}
}
CurrentADValue is:""
$CurrentADValue -eq $null =
True
Switch output (Below):
CurrentADValue is Null 1
CurrentADValue is Null 2
PS C:\Windows\system32>
By then, I'd lost my mind.
Can anyone explain this to me? How does the null output of the command, equal null in one comparison statement, but no in the switch statement, unless it's manually assigned $null?
I'm at a loss, and would greatly appreciate some clarification.

Reducing memory consumption of PowerShell script

I know this is pulling quite a bit of data, but at present it's capping my memory consumption when I run it on my local machine. The good news is, it's returning the output that I need. Can someone help me with performance optimization? So far, I haven't done much for fear of messing up a script that returns my desired output. Thanks in advance for any suggestions.
#// Start of script
#// Get year and month for csv export file
$DateTime = Get-Date -f "yyyy-MM"
#// Set CSV file name
$CSVFile = "C:\Temp\AD_Groups"+$DateTime+".csv"
#// Create emy array for CSV data
$CSVOutput = #()
Measure-Command {
#// Get all AD groups in the domain
$ADGroups = Get-ADGroup -Filter "GroupScope -ne 'DomainLocal' -AND GroupCategory -eq 'Security' -AND Member -like '*'" -SearchBase "OU=SHS, DC=shs, DC=net" -Properties Member #-ResultSetSize 1000 Name -like '*''s*' -AND
#// Set progress bar variables
$i=0
$tot = $ADGroups.count
foreach ($ADGroup in $ADGroups) {
#// Set up progress bar
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot * 100)
#// Ensure Members variable is empty
$Members = ""
#// Get group members which are also groups and add to string
$MembersArr = Get-ADGroup $ADGroup.DistinguishedName -Properties Member | Select-Object -ExpandProperty Member
if ($MembersArr) {
foreach ($Member in $MembersArr) {
$ADObj = Get-ADObject -filter {DistinguishedName -eq $Member}
#// Initialize regex variable
$matches = ""
if ($ADObj.ObjectClass -eq "user") {
$UserObj = Get-ADObject -filter {DistinguishedName -eq $Member}
$match = $UserObj -match '\([a-zA-Z0-9]+\)'
$empid=$matches[0] -replace ".*\(","" -replace "\)",""
if ($UserObj.Enabled -eq $False) {
continue
}
$Members = $empid
}
# Check for null members to avoid error for empty groups
if ([string]::IsNullOrEmpty($Members)) {
continue
}
$HashTab = [ordered]#{
"GroupName" = $ADGroup.Name -replace "'s", "''s"
"GroupCategory" = $ADGroup.GroupCategory
"GroupScope" = $ADGroup.GroupScope
"MemberID" = if([string]::IsNullOrEmpty($empid)){""}
else{$empid}
}
#// Add hash table to CSV data array
$CSVOutput += New-Object PSObject -Property $HashTab
}
}
#// Export to CSV files
$CSVOutput | Sort-Object Name, Member | Export-Csv $CSVFile -NoTypeInformation
}
}
I've experienced this too with code that loops through thousands of accounts. The problem is that the garbage collector doesn't have time during the loop to clean up, since your code is constantly doing something. In .NET, I'd call .Dispose() manually to make sure stuff is cleaned up, but here you can't.
You can try calling [System.GC]::Collect() after you assign each variable in the loop. For example, after $MembersArr = and after $ADObj = to (hopefully) make it deallocate the memory used for the previous value.
Also, I think that $UserObj = Get-ADObject... line should be calling Get-ADUser, not Get-ADObject. As it is, $UserObj.Enabled will never have a value and your continue will never be hit.
But you can save yourself the use of Get-ADUser entirely by asking for the userAccountControl value in Get-ADObject and using that to determine if the user is disabled. For example:
$ADObj = Get-ADObject -filter {DistinguishedName -eq $Member} -Properties userAccountControl
# Clean up the old $ADObj value
[System.GC]::Collect()
#// Initialize regex variable
$matches = ""
if ($ADObj.ObjectClass -eq "user") {
$match = $ADObj -match '\([a-zA-Z0-9]+\)'
$empid=$matches[0] -replace ".*\(","" -replace "\)",""
if ($ADObj.userAccountControl -band 2) {
continue
}
$Members = $empid
}
The $ADObj.userAccountControl -band 2 condition checks is a bitwise AND comparison to check if the second bit of the userAccountControl value is set, which means that the account is disabled.

Powershell: Get users with multiple specified Product Licences

I'm trying to get output from O365 via PowerShell for users with specific product licenses assigned.
There's plenty of articles out there of how to get this information but not found information explaining how to grab users with multiple specified products.
So far I have the following (taken from here):
Get-MsolUser -All |
Where-Object {
($_.licenses).AccountSkuId -match "EXCHANGEENTERPRISE" `
-and ($_.licenses).AccountSkuId -match "EMS" `
-and ($_.licenses).AccountSkuId -notmatch "ENTERPRISEPACK"
} |
Select-Object displayname,userprincipalname,{$_.Licenses.AccountSkuId} |
Export-Csv "C:\Temp\EOP2+EMSLicensedUsers.csv" -NoTypeInformation
However, this still shows users who have ENTERPRISEPACK assigned.
PowerShell operators do not work like you think they do.
.AccountSkuId is an array of values. Doing AccountSkuId -notmatch "ENTERPRISEPACK" does not tell you whether "ENTERPRISEPACK" is contained in that array or not. It gives you all the values from that array that do not match "ENTERPRISEPACK".
It's a filter. Try executing "1","2","3" -notmatch "3" to see what I mean.
Therefore, if even one value in AccountSkuId does not match "ENTERPRISEPACK", you still get some values back, and "some values" (i.e. a non-empty list) evaluates to $true in a Boolean expression.
You wanted to write this:
Get-MsolUser -All |
Where-Object {
($_.licenses).AccountSkuId -match "EXCHANGEENTERPRISE"
-and ($_.licenses).AccountSkuId -match "EMS"
-and -not (($_.licenses).AccountSkuId -match "ENTERPRISEPACK")
} |
Select-Object displayname,userprincipalname,{$_.Licenses.AccountSkuId} |
Export-Csv "C:\Temp\EOP2+EMSLicensedUsers.csv" -NoTypeInformation
Note the change. ($_.licenses).AccountSkuId -match "ENTERPRISEPACK" gives you all values that match "ENTERPRISEPACK" (normally 1 or 0) and the -not simply negates that result.
Other things to try with PowerShell operators:
1,2,2,3 -eq 2
1,2,2,3 -ne 2
1,2,2,3 -gt 1
"hallo","hello","foo" -like "*ll*"
"hallo","hello","foo" -replace 'l','L'
Keep in mind that PowerShell operates on lists when it can. A single value is nothing but a list of length 1.

Data changing between functions

After looking at what I could find, a few culprits that i've looked at would be the return being unrolled into a simple String, or other data being in the pipe.
My problem is cmdlets run against the data in the 1st function, do not work on the data by the time it reaches the 2nd function.
function Get-FileInput() {
$fileData = Import-Csv $filePath -Delimiter "`t"
if ($fileData | Get-Member | Where-Object {$_.name -eq "DistinguishedName" -or $_.name -eq "dn" -or $_.name -eq "name"})
{
Write-Host -ForegroundColor Green "Confirmed.
Processing"$fileData.Count"objects."
$invalidFilePath = $false
$fileData
}
$data = Get-FileInput
Write-Host $data
}
$data = Get-FileInput
Set-MultipleComputersEnabledStatus -data $data
Everything works fine up until this point. Before it gets passed into the 2nd function, it can be used with Format-Table and and Get-Member and everything appears perfectly fine. Inside Set-MultipleComputersEnabledStatus however, I can't perform the exact same checks on the $data object that I had done inside Get-FileInput
It is looking for a property named "DistinguishedName" which it finds in the 1st function, but won't recognize in the data by the time it gets to the 2nd function.
if ($filterType -eq "DistinguishedName" -and $data | Get-Member | Where-Object {$_.Name -eq 'DistinguishedName'})
{
Write-Host -ForegroundColor Green "Valid filters. Processing list."
}
I assume this means that after being passed in as an argument to the 2nd function, PowerShell changed the data for me somehow, but I can't find anything documenting what it did, or how to prevent it/work around it.
You need to use brackets around the embedded command part so that it is executed first and then the result can be evaluated:
if ($filterType -eq "DistinguishedName" -and ($data | Get-Member | Where-Object {$_.Name -eq 'DistinguishedName'})) { }