Data changing between functions - powershell

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

Related

Powershell if statement

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.

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.

Using Variable in Filter

I'm trying to query AD for a list of users from their Surname, which are help in a list.
I've tried most of the afternoon, but I just get a blank Excel sheet.
Also I want to know if there is more than one person with that username in AD, no idea how to even start with that one.
What I have so far:
Import-module ActiveDirectory
$names = get-content c:\tempfiles\Final.txt
$names | ForEach-Object {
$ADUserParams=#{
'Searchbase' = 'OU=Administrators,OU=Locations,DC=The,DC=group,DC=com'
'Searchscope'= 'Subtree'
}
get-aduser #ADUserParams -filter 'surname -like "$Names*"' | Select-Object Samaccountname, UserPrincipalName | export-csv C:\TempFiles\Usernames.csv
}
Do I even need a filter if it's a foreach-object? And is there a way to then check AD within that OU if there are more than one surname that are the same, and how would I count them? I can pull out a list of users surnames and then run the following, but it's then a manual task to locate the missing names. (If that makes sense)
What I have for that so far is:
get-content C:\TempFiles\Users.txt | sort -u > C:\TempFiles\users_cleaned.txt
This should do it (however is untested as I don't have access to an AD right now):
Import-module ActiveDirectory
$names = get-content c:\tempfiles\Final.txt
$ADUserParams=#{
'Searchbase' = 'OU=Administrators,OU=Locations,DC=The,DC=group,DC=com'
'Searchscope'= 'Subtree'
}
$names | ForEach-Object {
$CurrentUser = get-aduser #ADUserParams -filter "surname -like '$_*'" | Select-Object Samaccountname, UserPrincipalName
If ($CurrentUser) {
If ($CurrentUser.Count -gt 1){ $DuplicateSurname = $true }Else{ $DuplicateSurname=$false }
$CurrentUser | ForEach-Object {
$_ | Add-Member -MemberType NoteProperty -Name DuplicateSurname -Value $DuplicateSurname
Write-Output $_
}
} Else {
Write-Warning "$_* did not matched any users."
}
} | export-csv C:\TempFiles\Usernames.csv
Explanation:
Within a ForEach-Object loop the current item in the pipeline is represented by $_. You also need to use double quotes for the filter string, as variables (like $_) are expanded in double quoted strings, not single quoted strings.
You don't need to declare your $ADUserParams hashtable within the loop (that's wasteful) so I moved it outside.
The result of Get-ADUser will be returned to the pipeline, so finally I moved the | export-csv outside of the ForEach-Object so that the result of the processing is piped in to it. I think without this you'd only get the final result.
"Also I want to know if there is more than one person with that username in AD"
To handle this I have put a second ForEach-Object that loops through every user returned in to $CurrentUser and adds a "DuplicateSurname" property to the object (which should then be an additional column in your CSV) based on whether the count of $CurrentUser is more than 1 or not.
Finally we have to make sure that the contents of $_ are put back in to the pipeline which we do with Write-Object $_.

Trying to rename several account types at once based on current displayName

This morning some awesome people helped me make a script to move user accounts based on their displayName to a certain OU. I tested and it worked. I cannibalized the script to make another one that will rename the same accounts based off of the same criteria. I've gone through several errors but basically it all boils down to "I am having an identity crisis!". I can't seem to figure out exactly what I need to input as the $Identity. Here is what I have:
Import-Module ActiveDirectory
$Renames = #(
#{
Filter = 'DisplayName -like "*Supply*"'
NewName = "Supplies"
},
#{
Filter = 'DisplayName -like "*Accountant*"'
NewName = "Accounting"
}
) | ForEach-Object {New-Object -TypeName PSCustomObject -Property $_}
$OriginOU = "OU=Test,OU=Standard Users,OU=Domain Users,DC=com"
foreach ($Rename in $Renames) {
Get-ADUser -SearchBase $OriginOU -Filter $Rename.Filter -Properties displayName |
Where-Object {($_.Enabled -eq 'True') -and ($_.DistinguishedName -notlike '*DontTouch*')} |
%{Set-ADUser $_ -DisplayName {$_.DisplayName -replace '(.EPSILON ).+',"`$1$Rename.NewName"}}
}
You can't use the current object variable ($_) if you have Set-ADUser read directly from the pipeline. And since Set-ADUser apparently doesn't play nice with scriptblock arguments, you have to put the statement in a loop:
... | % { Set-ADUser $_ -DisplayName ($_.DisplayName -replace '(.EPSILON ).+',"`$1$($Rename.NewName)") }
Note that if you want to expand object properties inside a string you have to put $Rename.NewName in a subexpression ($()), otherwise the whole object $Rename would be stringified and the string ".NewName" would be appended to it.

Powershell Select-Object Formatting

$output = $data | Where-Object {$_.Name -eq "$serverName"} | Select-Object -Property Description1,Version | Where-Object {$_.Description1 -eq "Power controller Firmware"} | Select-Object -Property Version
Write-Host $output
Gives me the following output:
#{Version=3.4}
So $data is an array and I select what I want form it and assign it to a variable to eventually be inputted into a excel file but no matter what I seem to try I cant just select "3.4" Instead it selects like the above (#{Version=3.4}). Doesn't anybody know how to just select the "3.4" within my command?
Just replace last line with:
foreach( $out in $output )
{
Write-Host $out.Version
}
In fact your $output variable contains an array so you need to go through it with a foreach loop.
Then you can Write-Host or do anything with the Version property.
As stated by #okaram, if you want to make the same kind of looping but after a pipe you can do it this way:
$output | ForEach-Object {Write-Host $_.Version}
or
$output | %{Write-Host $_.Version}
Your last expression of
Select-Object -Property Version
Keeps the entire object in the pipeline, but filters down the properties to only Version. However, the -ExpandProperty will put the property value itself in the pipeline.
Select-Object -ExpandProperty Version
That should return the "3.4" result you expect.
Please try the following code:
$data | Where-Object {$_.Name -eq "$serverName"} | Select-Object -Property
Description1, Version | Where-Object {$_.Description1 -eq "Power controller
Firmware"} | write-Host $_.Version