How to "if" on multiple entries of an array? - powershell

I'm wondering if there's a way to simplify the code below by having an if statement that checks if a value matches one of the entries of an array...
The code I ended up with:
foreach ($sourceLine in $source) {
$sourceAVTrim = $sourceLine.ID.Remove(0, 1)
$sourceAV = "av" + $sourceAVTrim
if ($SourceLine.MAC -like "VCO*" -or $SourceLine.MAC -like "FOO*" -or $SourceLine.MAC -like "HOM*" -or $SourceLine.MAC -like "EOP*" -or $SourceLine.MAC -like "PCP*" -or $SourceLine.MAC -like "BUI*" -or $SourceLine.MAC -like "DML*") {
if ($SourceLine.MAC -like "*ADM" -or $SourceLine.MAC -like "*SAL" -or $SourceLine.MAC -like "*PLA" -or $SourceLine.MAC -like "*PLN" -or $SourceLine.MAC -like "*PLC" -or $SourceLine.MAC -like "*PLS") {
Write-Host "$($SourceAV) will NOT receive SMS - $($SourceLine.MAC)" -ForegroundColor Red
} else {
Write-Host "$($SourceAV) will receive SMS - $($SourceLine.MAC)" -ForegroundColor Green
}
} else {
Write-Host "$($SourceAV) will NOT receive SMS - $($SourceLine.MAC)" -ForegroundColor Red
}
}
But how can I set 2 arrays instead and have IF check my value against each entry?
$startMACs = #("VCO","FOO","HOM","EOP","PCP","BUI","DML")
$endMACs = #("ADM","SAL","PLA","PLN","PLC","PLS")

For exact matches you could use the -contains or -in operator. However, in your case you want a partial match against multiple strings, so I'd go with a regular expression.
$startMACs = 'VCO', 'FOO', 'HOM', 'EOP', 'PCP', 'BUI', 'DML'
$pattern = ($startMACs | ForEach-Object { [Regex]::Escape($_) }) -join '|'
if ($SourceLine.MAC -match "^($pattern)") {
...
} else {
...
}
^ anchors the expression at the beginning of a line, so you'll get the matches beginning with those substrings.
To anchor the expression at the end of a line (so you'll get the matches ending with the substrings) replace "^($pattern)" with "($pattern)$".
$pattern is constructed as an alternation (VCO|FOO|HOM|...), meaning "match any of these substrings" ("VCO" or "FOO" or "HOM" or ...).
Escaping the individual terms before building the pattern is so that characters with a special meaning in regular expressions (like for instance . or *) are treated as literal characters. It's not required in this case (since there are no special characters in the sample strings), but it's good practice so that nothing unexpected happens should someone update the list of strings with values that do contain special characters.

Since your patterns all start with a 3 letter code, one possibility is to grab the start of the target string and match using the -in operator with the pattern collection:
$startMACs = #("VCO","FOO","HOM","EOP","PCP","BUI","DML","HDOM")
if($SourceLine.MAC.Substring(0,3) -in $startMACs) {
# Do something
}
So, if $SourceLine.MAC is, say, EOP123, the substring() call grabs the EOP part, then compares it to each entry in the $startMACs array, returning true if it finds a match, and false otherwise - should be true in this example.
You can do something similar for the end patterns:
$endMACs = #("ADM","SAL","PLA","PLN","PLC","PLS")
if($SourceLine.MAC.Substring($SourceLine.MAC.Length - 3,3) -in $endMACs) {
# Do something
}

Related

Is there a better way to delete shapes in a Word document, with PowerShell?

I have been trying to loop through all the shapes in a Word document, find the shapes, ungroup them, then delete the ones with names "-like" "Straight Arrow Connector*," etc. However, I am doing something wrong and can't figure out what. It's ungrouping all of the shapes; however, it is not deleting every shape.
I tried the following for loop:
foreach($shape in $doc.Shapes){
if($shape.Name -like "Oval*" -or $shape.Name -like "Oval *"){
if($shape -ne $null) { #check if the shape exists before trying to delete it
$shape.Select()
$shape.Delete()
}
}
elseif($shape.Name -like "Straight Arrow Connector*" -or $shape.Name -like "Straight Arrow Connector *"){
if($shape -ne $null) { #check if the shape exists before trying to delete it
$shape.Select()
$shape.Delete()
}
}
elseif($shape.Name -like "Text Box *" or $shape.Name -like "Text Box*"){
if($shape -ne $null) { #check if the shape exists before trying to delete it
$shape.Select()
$shape.Delete()
}
}
}
But like I said, it didn't delete every shape, even they had names like the ones I was searching for. Is there a better way?
Then you can simply surround that with a loop iterating over the various shape names you wish to delete like this:
foreach ($name in 'Oval', 'Straight Arrow Connector', 'Text Box') {
$shapes = #($doc.Shapes | Where-Object {$_.Name -like "$name*"})
while ($shapes.Count -gt 0) {
$shapes[0].Delete()
}
}
I realized after posting that I should store the shapes in an array and use a while loop to delete everything inside the array. I did this and it worked:
$shapes2 = $doc.Shapes
$shapesOval = #()
foreach($shape in $shapes2)
{
if($shape.Name -like "Oval*" -or $shape.Name -like "Oval *"){
$shapesOval += $shape
}
}
while($shapesOval.Count -gt 0)
{
$shapesOval[0].Delete()
}

Check if string is in list of strings

Context:
We are making an API to get a list of all VMs and the filter it, using if loops, to return only VMs with name starting only with the values in $MachineList.
The list of servers is split in 2:
set 1: srv-a-1, srv-a-2, srv-b-1, srv-b-2, srv-c-1, srv-c-2, etc.
set 2: tst-a-1, tst-a-2, tst-b-1, tst-b-2, tst-c-1, tst-c-2, etc.
This is the script:
$EnvironmentList = "Environments-4" -or "Environments-5" -or "Environments-41" -or "Environments-61"
$MachineList = "srv-a*" -or "srv-b*" -or "srv-c*" -or "srv-d*" -or "srv-e*" -or "srv-f*" -or "srv-g*" -or "srv-h*" -or" srv-i*" -or "srv-j*" -or "srv-k*" -or "srv-l*"
function CheckService {
$MachinesRequest = (Invoke-WebRequest -Method Get -Headers #{"X-system-ApiKey"="Hashed-API-Key-Value"} -URI https://url-to-site.local/api/machines/all).Content | ConvertFrom-Json
foreach ($Machine in $MachinesRequest) {
if ($EnvironmentList -contains $Machine.EnvironmentIds) {
if ($MachineList -contains $Machine.Name) {
$Machine.Name
}
}
}
}
CheckService
We're trying to return just the items which match the values in the machine list however this is returning the full list of machines (both srv* and tst*).
First and foremost, $MachineList = "srv-a*" -or "srv-b*" -or ... won't do what you apparently think it does. It's a boolean expression that evaluates to $true, because PowerShell interprets non-empty strings as $true in a boolean context. If you need to define a list of values, define a list of values:
$MachineList = "srv-a*", "srv-b*", ...
Also, the -contains operator does exact matches (meaning it checks if any of the values in the array is equal to the reference value). For wildcard matches you need a nested Where-Object filter
$MachineList = "srv-a*", "srv-b*", "srv-c*", ...
...
if ($MachineList | Where-Object {$Machine.Name -like $_}) {
...
}
A better approach in this scenario would be a regular expression match, though, e.g.:
$pattern = '^srv-[a-l]'
...
if ($Machine.Name -match $pattern) {
...
}
use -eq for an exact match. use -match or -contains for a partial string match
$my_list='manager','coordinator','engineer', 'project engineer',
$retval=$False
if ($dd_and_pm -eq "engineer")
{
$retval=$True
}

Pipe a string through multiple if-statements with an else for each

Essentially I have an If statement that has two conditions using -and, which a string is then run through. However, what I really want is the string to be run through the first condition, then if that is true, it checks for the second, and if that is true it does one thing, and if it is false do something else.
I currently have:
if(($_ -match "/cls") -and ($env:UserName -eq $Name)){cls}
I know I can do what I want with:
if(($_ -match "/cls") -and ($env:UserName -eq $Name)){cls}
elseif(($_ -match "/cls") -and ($env:UserName -ne $Name)){OTHER COMMAND}
But I would like to know if there was a more simple way.
(The result is already being piped in from somewhere else, hence the $_)
Move the first match to the outer scope.
if($_ -match "/cls")
{
if ($env:UserName -eq $Name))
{
cls
}
else
{
other command
}
}
I would write two if statements to improve readabilty. This way, you also need the pipeline value only once::
if($_ -match "/cls")
{
if ($env:UserName -eq $Name)
{
cls
}
else
{
#OTHER COMMAND
}
}

PowerShell wildcard in the middle of a string

I am getting some content from a text file and loading it into a variable.
Once I have the data I iterate through each row of data excluding some values being added into my array like this..
foreach($row in $data)
{
if($row)
{
Write-Host $row
[Array]$index = $row.Split(" ") -ne "[needWildCardHere]" -ne "Value2" -ne "Value3" -ne "Value4" -ne "Value5"
}
}
Each value that matches each string I give, will not be added into the array.
Notice my very first value here [Array]$index = $row.Split(" ") -ne "[needWildCardHere]"
I have a bunch of values in the $row of $data that I have that have a timestamp similar to this:
[10:49:32] [09:32:57] [06:17:15] [06:17:15]
Can I put a wild card inbetween brackets so I do not add any value that has [brackets] into my array?
Feel free to ask me if something is unclear,
Thanks
If you want to use a wildcard match, you need to change from using -ne to -notlike.
If you want to use a regular expression, you'd use -notmatch.
see:
Get-Help about_comparison_operators
An example:
[Array]$index = $row.Split(" ") -notlike "*`[*`]*" -ne "Value2" -ne "Value3" -ne "Value4" -ne "Value5"
Note: the square brackets are considered part of the wildcard set, so to match them literally they need to be escaped with backticks.
Try -notmatch "\[.*\]" instead of -ne
Personally I'd split and then filter since it seems to give more versatile filtering I think:
$Exclusions = #("value1","value2","value3")
gc c:\temp\test.txt |%{$_.split(" ") |?{$_ -notin $exclusions -and $_ -notmatch "\[.*?]" -and !([string]::IsNullOrWhiteSpace($_))}}

Export function

I am trying to create a function in which a user has to give a filename that can not containt an empty string. Besides that, the string can not contain a dot. When I run this function, I keep looping when I enter "test" for example. Any idea as to why?
function Export-Output {
do {
$exportInvoke = Read-Host "Do you want to export this output to a new .txt file? [Y/N]"
} until ($exportInvoke -eq "Y" -or "N")
if ($exportInvoke -eq "Y") {
do {
$script:newLog = Read-Host "Please enter a filename! (Exclude the extension)"
if ($script:newLog.Length -lt 1 -or $script:newLog -match ".*") {
Write-Host "Wrong input!" -for red
}
} while ($script:newLog.Length -lt 1 -or $script:newLog -match ".*")
ni "$script:workingDirectory\$script:newLog.txt" -Type file -Value $exportValue | Out-Null
}
}
EDIT:
On a related note:
do {
$exportInvoke = Read-Host "Do you want to export this output to a new .txt file? [Y/N]"
} until ($exportInvoke -eq "Y" -or "N")
When I use these lines of code I can simply hit enter to circumvent the Read-Host. When I replace "Y" -or "N" with simply "Y" it does not. Any idea as to why this is happening?
The -match operator checks against a regular expression, so this:
$script:newLog -match ".*"
is testing if the filename contains any charachter except newline (.) 0 or more times (*). This condition will always be true, thus creating an infinite loop.
If you want to test for a literal dot, you must escape it:
$script:newLog -match '\.'
As for your other question, you're misunderstanding how logical and comparison operators work. $exportInvoke -eq "Y" -or "N" does not mean $exportInvoke -eq ("Y" -or "N"), i.e. variable equals either "Y" or "N". It means ($exportInvoke -eq "Y") -or ("N"). Since the expression "N" does not evaluate to zero, PowerShell interprets it as $true, so your condition becomes ($exportInvoke -eq "Y") -or $true, which is always true. You need to change the condition to this:
$exportInvoke -eq "Y" -or $exportInvoke -eq "N"
Use this to test your input:
!($script:newLog.contains('.')) -and !([String]::IsNullOrEmpty($script:newLog)) -and !([String]::IsNullOrWhiteSpace($script:newLog))
Your regular expression (-match ".*" is essentially matching on everything.