Powershell -like syntax with hyphens - powershell

This is probably a newb question, but I've spent a couple hours on this now.
I am creating a powershell script, trying to determine if an entry already exists.
(This is for an Exchange multi-tenant solution)
This works:
Get-GlobalAddressList | Where{$_.Identity -Like "\MyCompany_com*"}
But this fails:
Get-GlobalAddressList | Where{$_.Identity -Like "\MyCompany_com - GAL"}
For some reason I can't fathom, the spaces in the entry won't match.
Yes, I am certain that the entry \MyCompany_com - GAL exists.
I have tried every combination I can think of using -match, -eq, -contains
Any help is appreciated!
---- Edit ----------------------
Tried a new tact, still failing miserably:
$NewVal = "\MyCompany_com - GAL"
$Prop = Get-GlobalAddressList | Select Name
foreach($PropVal in $Prop.Name){
write-output "comparing: $NewVal to $PropVal"
if($NewVal -like $PropVal){write-output "MATCH"} else {write-output "no-match"}
}
The write-output 'shows' a match character for character.
I have scripted in many languages for over 3 decades, but this PowerShell crap has me baffled. #frustrated#
---- Edit #2 (showing output) ----------------------
comparing: MyCompany_com - GAL to MyCustomer_com - GAL
no-match
comparing: MyCompany_com - GAL to MyCompany_com - GAL
no-match
comparing: MyCompany_com - GAL to Default Global Address List
no-match
Any way to force a string comparison?
Are the space characters still messing me up?
---- Edit #3 (still trying) ----------------------
I created a new GlobalAddressList: "MCC-GAL" purposely with no spaces.
This still does not work:
Get-GlobalAddressList | Where{$_.Name -Like "MCC-GAL"}
However, this DOES match:
Get-GlobalAddressList | Where{$_.Name -Like "MCC?GAL"}
So in addition to the space characters, the hyphen (-) is also causing match problems. I did try to escape the hyphen: "\-", but still no match.
Is there ANY WAY to force a simple string comparison?
The method I am using to build the compared string will be what I need to match with.

While I had a work-around for GetAddressList, the next part of my script I was forced to figure out this issue.
Determination: I shared the same concern as #user2460798, but felt safe to disregard since I had copy/pasted to the power-shell line the GlobalAddressList name with the "normal" dash. As it turns out, the sample commands we copied contained the en-dash. Ouch, lesson learned. Over 14 hours wasted on this for my co-worker and me. #crying#
I finally stumbled upon a script, How to convert a ascii string into a decimal representative string in a powershell script?
, that with some slight modification I was able to reveal that the value stored in AD was in fact an "en-dash", [char]8211 (a normal dash is [char]045).
So, here is the command that will match the entry I was trying to retrieve:
Get-GlobalAddressList | Where{$_.Name.replace([convert]::ToChar(8211),"-") -eq "MyCompany_com - GAL"}

As it turns out, I was able to match on a different object in the GlobalAddressList object:
Get-GlobalAddressList | Where{$_.ConditionalCustomAttribute1 -eq "MMC"}
With this being a multi-tenant solution, we have been adding a value to the ConditionalCustomAttribute1 object, that (in our situation) will be unique and therefore suitable for testing the existence of.
This doesn't answer the original question, but it solves my scripting task.

What do you get if you enter just type in:
"\MyCompany_com - GAL" -like "\MyCompany_com - GAL"
If that works then it strongly suggests that the $_.Identity property has some characters in it that are different than what you think. Here is one way you can see exactly what characters a string contains:
[char[]]$stringWithOddCharacters # breaks the string into characters
[byte[]][char[]]$stringWithOddCharacters # converts each character into a byte
So you could do something like
Get-GlobalAddressList | foreach-object {
if ($_.Identity -Like "\MyCompany_com*") {
[char[]]($_.Identity.ToString())
[byte[]][char[]]($_.Identity.ToString())
}
}
to see exactly what's in the Identity property. Note that if Identity contains Unicode characters that don't convert to ASCII you'll need to change [byte[]] to [int16[]]

I ran across this post while searching for the -Filter switch syntax. Your first script example was correct because the -like switch should be used with a wildcard character. Try replacing -Like with -Match (no wildcard) in your second script example.

Related

Powershell property select adding whitespace and header

I am trying to write a script to pull all e-mail addresses but I am finding that the output of my first get command is adding alot of white space to the result text file
Get-AdUser -Filter * -Properties * | Select EmailAddress | Out-File "C:\T2\EMailAddresses.txt"
Can anyone tell me what I am doing wrong here?
You are seeing whitespace because not every AD account has a value assigned to that property in your environment so it appears as a blank line, I get the same thing upon a quick test.
This should help.
$Emails = Get-ADUser -Filter * -Properties EmailAddress
$Emails | select EmailAddress | Where {$_.EmailAddress -ne $null} | Out-File "C:\T2\EMailAddresses.txt"
td;dr
The following writes all email addresses to the target file, ignoring AD users that don't have one:
([string[]] (Get-AdUser -Filter * -Properties EmailAddress).EmailAddress) -ne '' |
Set-Content C:\T2\EMailAddresses.txt
By writing just the - non-empty - .EmailAddress property values to the file, you're avoiding the problems that stem from saving for-display formatted object representations, which is what your attempt does (see below).
Note that -ne '' acts as a filter here, because its LHS operand is an array; that is, the result of the operation is the sub-array of those LHS elements that aren't the empty string ('').
As for what you tried:
By using Out-File in combination with objects subject to PowerShell's output formatting system, you're saving a for-display representation of your objects to a file, which, in the case at hand includes a table header, a leading and a trailing blank line and - in Windows PowerShell (but no longer in PowerShell (Core) 7+) - right-space-padding to the full console-line width of each line.
Even though you're only asking for one property - EmailAddress - Select-Object outputs not just that property's value for each input object, but a [pscustomobject] instance with an .EmailAddress property, and the resulting objects are implicitly formatted with Format-Table.
To get just the EmailAddress property values, use Select-Object -ExpandProperty EmailAddress. The resulting string values are not subject to formatting, so your command would work as intended except that it would still include $null values from those AD users who happen not to have a value stored in their .EmailAddress property.
While it often won't matter, for string input it's slightly faster to use Set-Content than Out-File / >; note that in Windows PowerShell you'll end up with different character encodings by default (ANSI vs. UTF-16 LE a.k.a "Unicode") - use the -Encoding parameter as needed; PowerShell Core 7+ fortunately now consistently defaults to BOM-less UTF-8.
The - faster, but more potentially memory-intensive - alternative to using Select-Object -ExpandProperty EmailAddress for extracting the EmailAddress property values is to use member-access enumeration ((...).EmailAddress, as shown above).

How to get just a portion of the results from Select-Object

I have a simple question, I hope someone can help...
if I run this command;
get-pnpDevice -class display
I get the expected results in a table format. It displays the Status, Class, FriendlyName, and InstanceID.
Next, I want to get just the InstanceID, which I can do with this command;
Get-PnpDevice -Class display | Select-Object -Property instanceID
But what I am stuck on is that I just want to get the inner portion of the result, not the full result. In other words, on my system, if I run the above command, I get the following result;
PCI\VEN_8086&DEV_5912&SUBSYS_872F1043&REV_04\3&11583659&0&10
PCI\VEN_10DE&DEV_1B81&SUBSYS_85971043&REV_A1\4&35D4F288&0&0008
But what I am trying to get is the result without the PCI\ and anything after and including the ampersand, for example, I dont want to see the &REV_04\3&11583659&0&10 or &REV_A1\4&35D4F288&0&0008
Any suggestions?
I tried the suggested calculated property and it returns something close to what I am trying to do, but not completely. I don't understand the calculated property, so please bear with me.... the result is as follows;
InstanceID
VEN_8086
VEN_10DE
It seems to cut-off the results. I had a look through the Microsoft Select-Object documentation and it describes a little bit about calculated properties, but it doenst say anything about the -replace parameter, so I am a little lost about how to work with this and fix it for my use. Is there a better documentation you can point me to learn more about this?
You will likely need a calculated property with Select-Object.
Get-PnpDevice -Class display |
Select-Object #{n='InstanceID';e={$_.InstanceId -replace '^PCI\\(.*?)&REV.*$','$1'}}
You can add other properties (comma-separated) along with the calculated property.
The -replace operator uses regex matching. The example here uses a capture group (.*?), which is named capture group 1 (referenced later with $1) to capture all characters between PCI\ and the first &REV. ^ is the start of a string. $ is the end of the string. .* greedily matches zero or more characters. Since \ is a special regex character, \ is used to escape it resulting in \\.
The e or expression part of the calculated property contains a script block (code within {}). The script block contents perform a string replacement on each InstanceId value ($_.InstanceId).

PowerShell Out-File

I am fairly new to PowerShell scripting. I have created a very simple script that pipes a couple of commands and displays on screen with different colors for different results.
I want to take the on screen data and put all of it into a txt file, which eventually I'll use for Zabbix alerts.
The script executes and creates the txt file, yet the txt file is empty.
Any assistance would be great!
Code:
Get-MailboxServer | Get-MailboxDatabaseCopyStatus | ForEach {
if ($_.Status.ToString() -notmatch "Mounted" -or $_.ContentIndexState.ToString() -notmatch "Healthy") {
Write-Host "$($_.Name) - $($_.Status) - $($_.ContentIndexState) - OK" -ForegroundColor Green
} else {
Write-Host "$($_.Name) - $($_.Status) - $($_.ContentIndexState) - FAILED" -ForegroundColor Red
}
} | Set-Content | Out-File c:\scripts\exchangedb.log
General rule is that write-host, writes only to the host (PowerShell console) and cannot be sent to any other output!
If you would like to create an output and store it into a file, you may use write-output cmdlet ... option one.
Or Option 2: Use two operations: 1. Write-Host and then 2. output to file ..
Also another hint, no need to use match in this case.
You may try to use -eq (equal) or -ne (not equal), if you are looking for equality. Note: Those operators do not understand wildcards!
If you would like to use wildcards, then you may check: -like and -notlike.
And finally, there is -match, or -notmatch, which understands also Regular Expressions.
It is not wrong, but a bit of overhead.
You may check more, about comparison operators here:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-6
And as you are new to PowerShell, also check any about_ section in PowerShell (or online). In PowerShell console run: get-help about_ and you will see a huge lists of conceptual topics which are reaaly well written with a great examples. Same could be found online.
Note: You may need to run update-help first to geth the full list (About 140+ topics).
And just to mention: Your script logic looks quite fine for a newbie, so keep it up! ;)
Hope it helps!
Best regards,
Ivan

Stripping Data From a String In Powershell

I'm pulling the hostnames from all computers in an AD domain and the current command formats it in url form with the hostname at the end. I just need the hostnames so I'd like to strip everything to the left of the last forward slash.
(([adsi]"WinNT://$((Get-WMIObject Win32_ComputerSystem).Domain)").Children).Where({$_.schemaclassname -eq 'computer'}) | %{ $_.Path }
It's outputting as it should, I just happen to just need the hostname, so instead of WinNT://subdomain.somedomain.local/hostname I just got hostname which I would then redirect to an output file.
You can use the -Split operator to help retrieve the data:
"WinNT://subdomain.somedomain.local/hostname" -Split "/" | Select-Object -Last 1
-Split "/" separates the value into an array of substrings using / as a delimiter. You can access the resulting parts using array indexes or Select-Object. Since you want the last value, you could alternatively access [-1] index of the resulting array (("WinNT://subdomain.somedomain.local/hostname" -Split "/")[-1]).
See About Split for more information and examples.
Just posting another option, and something else that may be useful. You can also split strings by their last index, which is the last time a character appears in it. From there you can use the Substring method to select the remainder of the string.
$lio = "WinNT://subdomain.somedomain.local/hostname".LastIndexOf('/')
"WinNT://subdomain.somedomain.local/hostname".Substring($lin + 1) # +1 to not include the slash
You can see all the methods for a string here
For things like this, I would also suggest looking at the ActiveDirectory module. You can run Get-ADComputer and select specific fields really easily.

Get Substring of value when using import-Csv in PowerShell

I have a PowerShell script that imports a CSV file, filters out rows from two columns and then concatenates a string and exports to a new CSV file.
Import-Csv "redirect_and_canonical_chains.csv" |
Where { $_."Number of Redirects" -gt 1} |
Select {"Redirect 301 ",$_.Address, $_."Final Address"} |
Export-Csv "testing-export.csv" –NoTypeInformation
This all works fine however for the $_.Address value I want to strip the domain, sub-domain and protocol etc using the following regex
^(?:https?:\/\/)?(?:[^#\/\n]+#)?(?:www\.)?([^:\/\n]+)
This individually works and matches as I want but I am not sure of the best way to implement when selecting the data (should I use $match, -replace etc) or whether I should do it after importing?
Any advice greatly appreciated!
Many thanks
Mike
The best place to do it would be in the select clause, as in:
select Property1,Property2,#{name='NewProperty';expression={$_.Property3 -replace '<regex>',''}}
That's what a calculated property is: you give the name, and the way to create it.Your regex might need revision to work with PowerShell, though.
I've realized now that I can just use .Replace in the following way :)
Select {"Redirect 301 ",$_.Address.Replace('http://', 'testing'), $_."Final Address"}
Based on follow-up comments, the intent behind your Select[-Object] call was to create a single string with space-separated entries from each input object.
Note that use of Export-Csv then makes no sense, because it will create a single Length column with the input strings' length rather than output the strings themselves.
In a follow-up comment you posted a solution that used Write-Host to produce the output string, but Write-Host is generally the wrong tool to use, unless the intent is explicitly to write to the display only, thereby bypassing PowerShell's output streams and thus the ability to send the output to other commands, capture it in a variable or redirect it to a file.
Here's a fixed version of your command, which uses the -join operator to join the elements of a string array to output a single, space-separated string:
$sampleCsvInput = [pscustomobject] #{
Address = 'http://www.example.org/more/stuff';
'Final Address' = 'more/stuff2'
}
$sampleCsvInput | ForEach-Object {
"Redirect 301 ",
($_.Address -replace '^(?:https?://)?(?:[^#/\n]+#)?(?:www\.)?([^:/\n]+)', ''),
$_.'Final Address' -join ' '
}
Note that , - PowerShell's array-construction operator - has higher precedence than the -join operator, so the -join operation indeed joins all 3 preceding array elements.
The above yields the following string:
Redirect 301 /more/stuff more/stuff2