Verifying a string is not empty/null in an If condition - powershell

I'm writing a script that will accept user input via Read-Host (set to $String), and I want to avoid any issues that could be caused by having a blank value for the variables. Since I'll be using this a lot, I want to implement it into a function that verifies no invalid characters are being used.
I thought I could use an if statement with ![string]::IsNullOrEmpty($String) as one of the conditions:
Function Test-ValidCharacters ($String, $ValidCharacters) {
if (($String -match $ValidCharacters) -and (!([string]::IsNullOrEmpty($String)))) {
return $true
}
else {return $false}
}
I also tried this:
Function Test-ValidCharacters ($String, $ValidCharacters) {
if (($String -match $ValidCharacters) -and ($String -ceq "")) {
return $true
}
else {return $false}
}
In both of these cases, I can just hit enter when presented with the $String's Read-Host prompt and the script will behave as if the function returned $True (and then later encounter fatal errors). The other half works - if I include characters not specified by $ValidCharacters the function returns $False as expected.
I am sure I'm missing something here. I even tried doing a second nested if statement and got the same result.
Edit: Here's the code snippet where I call the function and notice the issue.
$ValidCharacters = '[^a-zA-Z0-9]'
$FirstN = Read-Host -Prompt "New user's first name"
While (Test-ValidCharacters $FirstN $ValidCharacters -eq $false) {
Write-Output "$FirstN contains illegal characters. A-Z, a-z, and 0-9 are accepted."
$FirstN = Read-Host -Prompt "New user's first name"
}

Assuming $ValidCharacters isn't itself an empty string and contains an anchored character-range regex (regular expression) that covers the entire input string, such as ^[a-z0-9./:]+$, given that the -match operator matches any substring by default (note that a better name for the parameter is therefore something like $ValidationRegex):[1]
In the first function definition, the RHS of your -and operation is redundant - it adds nothing to the conditional, because if $String -match $ValidCharacters is $true, then so is ! [string]::IsNullOrEmpty($String), by definition.
Conversely, in the second function definition your -and operation always returns $false, because $String -ceq "" is by definition $false, if the LHS returned $true.
Assuming that your intent is to prevent empty or all-whitespace input and to ensure that any string - trimmed of incidental leading and/or trailing whitespace - is composed only of expected characters, use the following:
Function Test-ValidCharacters ($String, $ValidCharacters) {
# Note: no strict need for `return`.
$String.Trim() -match $ValidCharacters
}
[1] Alternatively, stick with $ValidCharacters and pass a regex that describes only a single valid character, such as '[a-z0-9./:]', and construct the entire-string matching regex inside the function with '^' + $ValidCharacters + '+$'

Related

Small problem with (if) command in powershell

First, let me show you the code
$url = Read-Host 'URL'
if ( $url -gt '.'){
msg * "The title does not contain(.)"
}
The code should alert you that the site does not have a dot
But either way the code alerts me
The code does not give me any error
The -gt operator does a case-insensitive lexical comparison, and where non-letters are involved, it uses case-folded Unicode ordering. The . is ordered before any of the letters, so virtually any URL, with or without a dot, will be lexically greater than the single dot you are comparing against.
If you want to test whether a string contains a dot, you should most likely be using the -match or -notmatch operators; note that this uses regular expressions.
$url = Read-Host 'URL'
if ( $url -notmatch '\.'){
msg * "The title does not contain(.)"
}

Ignore Tokens on the Front of a Regular Expression in Powershell

Hi so I have read over regular expressions and all that but don't really fully understand it. Just looking for a little help here after a lot of searching on here and google.
I have an XML file that I am editing but for now let's pretend I'm doing just a single string. This works great except that I lose Connection Database="SQLEventLog" text in the replace. What kind of ignore token do I use here?
Here is my code
$passedString = '<Connection Database="SQLEventLog" >Data
Source=;Initial Catalog=Connector;Integrated Security=True</Connection>'
search($passedString)
function search ($string)
{
$pattern = '*Data Source=*'
if ($string -like '*Data Source=*')
{
Write-Warning 'found'
$string = $string -replace '.*Data Source=*', 'Data
Source=localhost'
}
Write-Warning $string
}
So, a few things. Best-practice for defining function parameters is to use the Param() clause. Functions in PowerShell are not called with parenthesis, but are separated by spaces (e.g. Function arg1 arg2 Arrayarg3,Arrayarg3)
Additionally, the -like comparison operator does not use regex, it's a wildcard comparison. I've updated your example to accomplish your goal.
Function Search
{
Param($String)
If ($String -like '*Data Source=*')
{
Write-Warning 'found'
$string = $string -replace 'Data\sSource=', 'Data Source=localhost'
}
Write-Warning $string
}
$passedString = 'Data Source=;Initial Catalog=Connector;Integrated Security=True'
Search $passedString
Note: the -replace function DOES use regex for the first piece.

Pass a single space-delimited string as multiple arguments

I have a Powershell function in which I am trying to allow the user to add or remove items from a list by typing the word "add" or "remove" followed by a space-delimited list of items. I have an example below (slightly edited, so you can just drop the code into a powershell prompt to test it "live").
$Script:ServerList = #("Server01","Server02","Server03")
Function EditServerList (){
$Script:ServerList = $Script:ServerList |Sort -Unique
Write-host -ForegroundColor Green $Script:ServerList
$Inputs = $args
If ($Inputs[0] -eq "start"){
$Edits = Read-Host "Enter `"add`" or `"remove`" followed by a space-delimited list of server names"
#"# EditServerList $Edits
# EditServerList $Edits.split(' ')
EditServerList ($Edits.split(' ') |Where {$_ -NotLike "add","remove"})
EditServerList start
} Elseif ($Inputs[0] -eq "add"){
$Script:ServerList += $Inputs |where {$_ -NotLike $Inputs[0]}
EditServerList start
} Elseif ($Inputs[0] -eq "remove"){
$Script:ServerList = $Script:ServerList |Where {$_ -NotLike ($Inputs |Where {$_ -Notlike $Inputs[0]})}
EditServerList start
} Else {
Write-Host -ForegroundColor Red "ERROR!"
EditServerList start
}
}
EditServerList start
As you can see, the function takes in a list of arguments. The first argument is evaluated in the If/Then statements and then the rest of the arguments are treated as items to add or remove from the list.
I have tried a few different approaches to this, which you can see commented out in the first IF evaluation.
I have two problems.
When I put in something like "add Server05 Server06" (without quotes) it works, but it also drops in the word "add".
When I put in "remove Server02 Server03" (without quotes) it does not edit the array at all.
Can anybody point out where I'm going wrong, or suggest a better approach to this?
To address the title's generic question up front:
When you pass an array to a function (and nothing else), $Args receives a single argument containing the whole array, so you must use $Args[0] to access it.
There is a way to pass an array as individual arguments using splatting, but it requires an intermediate variable - see bottom.
To avoid confusion around such issues, formally declare your parameters.
Try the following:
$Script:ServerList = #("Server01", "Server02", "Server03")
Function EditServerList () {
# Split the arguments, which are all contained in $Args[0],
# into the command (1st token) and the remaining
# elements (as an array).
$Cmd, $Servers = $Args[0]
If ($Cmd -eq "start"){
While ($true) {
Write-host -ForegroundColor Green $Script:ServerList
$Edits = Read-Host "Enter `"add`" or `"remove`" followed by a space-delimited list of server names"
#"# Pass the array of whitespace-separated tokens to the recursive
# invocation to perform the requested edit operation.
EditServerList (-split $Edits)
}
} ElseIf ($Cmd -eq "add") {
# Append the $Servers array to the list, weeding out duplicates and
# keeping the list sorted.
$Script:ServerList = $Script:ServerList + $Servers | Sort-Object -Unique
} ElseIf ($Cmd -eq "remove") {
# Remove all specified $Servers from the list.
# Note that servers that don't exist in the list are quietly ignored.
$Script:ServerList = $Script:ServerList | Where-Object { $_ -notin $Servers }
} Else {
Write-Host -ForegroundColor Red "ERROR!"
}
}
EditServerList start
Note how a loop is used inside the "start" branch to avoid running out of stack space, which could happen if you keep recursing.
$Cmd, $Servers = $Args[0] destructures the array of arguments (contained in the one and only argument that was passed - see below) into the 1st token - (command string add or remove) and the array of the remaining arguments (server names).
Separating the arguments into command and server-name array up front simplifies the remaining code.
The $var1, $var2 = <array> technique to split the RHS into its first element - assigned as a scalar to $var1 - and the remaining elements - assigned as an array to $var2, is commonly called destructuring or unpacking; it is documented in Get-Help about_Assignment Operators, albeit without giving it such a name.
-split $Edits uses the convenient unary form of the -split operator to break the user input into an array of whitespace-separated token and passes that array to the recursive invocation.
Note that EditServerList (-split $Edits) passes a single argument that is an array - which is why $Args[0] must be used to access it.
Using PowerShell's -split operator (as opposed to .Split(' ')) has the added advantage of ignoring leading and trailing whitespace and ignoring multiple spaces between entries.
In general, operator -split is preferable to the [string] type's .Split() method - see this answer of mine.
Not how containment operator -notin, which accepts an array as the RHS, is used in Where-Object { $_ -notin $Servers } in order to filter out values from the server list contained in $Servers.
As for what you tried:
EditServerList ($Edits.split(' ') |Where {$_ -NotLike "add","remove"}) (a) mistakenly attempts to remove the command name from the argument array, even though the recursive invocations require it, but (b) actually fails to do so, because the RHS of -like doesn't support arrays. (As an aside: since you're looking for exact strings, -eq would have been the better choice.)
Since you're passing the arguments as an array as the first and only argument, $Inputs[0] actually refers to the entire array (command name + server names), not just to its first element (the command name).
You got away with ($Inputs[0] -eq "add") - even though the entire array was compared - because the -eq operator performs array filtering if its LHS is an array, returning a sub-array of matching elements. Since add was among the elements, a 1-element sub-array was returned, which, in a Boolean context, is "truthy".
However, your attempt to weed out the command name with where {$_ -NotLike $Inputs[0]} then failed, and add was not removed - you'd actually have to compare to $Inputs[0][0] (sic).
Where {$_ -NotLike ($Inputs |Where {$_ -Notlike $Inputs[0]})} doesn't filter anything out for the following reasons:
($Inputs |Where {$_ -Notlike $Inputs[0]}) always returns an empty array, because, the RHS of -Notlike is an array, which, as stated, doesn't work.
Therefore, the command is the equivalent of Where {$_ -NotLike #() } which returns $True for any scalar on the LHS.
Passing an array as individual arguments using splatting
Argument splatting (see Get-Help about_Splatting) works with arrays, too:
> function foo { $Args.Count } # function that outputs the argument count.
> foo #(1, 2) # pass array
1 # single parameter, containing array
> $arr = #(1, 2); foo #arr # splatting: array elements are passed as indiv. args.
2
Note how an intermediate variable is required, and how it must be prefixed with # rather than $ to perform the splatting.
I'd use parameters to modify the ServerList, this way you can use a single line to both add and remove:
Function EditServerList {
param(
[Parameter(Mandatory=$true)]
[string]$ServerList,
[array]$add,
[array]$remove
)
Write-Host -ForegroundColor Green "ServerList Contains: $ServerList"
$Servers = $ServerList.split(' ')
if ($add) {
$Servers += $add.split(' ')
}
if ($remove) {
$Servers = $Servers | Where-Object { $remove.split(' ') -notcontains $_ }
}
return $Servers
}
Then you can call the function like this:
EditServerList -ServerList "Server01 Server02 Server03" -remove "Server02 Server03" -add "Server09 Server10"
Which will return:
Server01
Server09
Server10

If Statement -match or -contains not returning correct condition

We are updating our SIP for email addresses and we are going to use a PowerShell script to monitor when AD is updated and then update the local machine.
I'm to the part where I split the email address at the # symbol and I'm looking to the left of the # symbol.
Example:
FirstName.LastName#someplace.com
I can split it correctly where I just get
FirstName.LastName
but when I go to check the condition if it contains a ., I can't get a correct true of false.
Example:
$sipaddress = "FirstName.LastName#someplace.com"
$splitname = $sipaddress.Split("#")[0]
# at this point, $splitname varible will contain "FirstName.LastName"
if ($splitname -match '.') {
Write-Host "TRUE"
} else {
Write-Host "False"
}
# this returns TRUE which is perfect
BUT, if I change the variable to like this for testing
$sipaddress = "FirstNameLastName#someplace.com"
the $splitname variable will contain FirstNameLastName, and it still returns TRUE when that is not correct. It should return false because there is no ..
What am I doing wrong? I tried to use -contains in the if statement but that does not work either.
Is there a way to try and check for a -match for the .?
The -match operator does a regular expression match, so matching . will match any character except newlines, not just dots. If you want to match a literal dot you need to escape it (\.) or do a different kind of comparison, for instance a wildcard match with the -like operator (-like '*.*') or by using the Contains() method of the string object.
The -contains operator checks if an array contains a particular element. To be able to use that operator you'd need to convert your string to a character array:
[char[]]$splitname -contains '.'
I wouldn't recommend doing this, though. The other methods are more convenient.
Modified code:
$sipaddress = "FirstName.LastName#someplace.com"
$localpart, $domain = $sipaddress -split '#'
if ($localpart.Contains('.')) {
Write-Host 'True'
} else {
Write-Host 'False'
}

elseif statement containing multiple expressions separated by -or failing

I have created a PowerShell script which is basically a simple calculator. The script needs to be passed 3 parameters, the first one being a number, the second one being an arithmetic operator (+ - * /) and the third one being another number, after which the script returns the calculated value.
Initially, the script worked being like this:
param (
[double]$num1,
[string]$op,
[double]$num2
)
switch ($op) {
"+" {write-host $($num1+$num2)}
"-" {write-host $($num1-$num2)}
"*" {write-host $($num1*$num2)}
"/" {write-host $($num1/$num2)}
}
However, I want to make the script a bit more complex by throwing a different error message for each of these situations:
When no parameters are passed, the message would be "You haven't specified any parameters".
When the second parameter isn't either "+", "-", "*" or "/", the message would be
"You haven't specified a valid operator (+ - * /) as the second parameter".
When the previous 2 situations don't occur but no third parameter (the 2nd number) is passed, the message would be "You haven't specified the second number".
So, with this in mind, I made some changes to the script ending up like this:
param (
[double]$num1,
[string]$op,
[double]$num2
)
if ($num1 -eq "") {
write-host "You haven't specified any parameters"
} elseif (($op -ne "+") -or ($op -ne "-") -or ($op -ne "*") -or ($op -ne "/")) {
write-host "You haven't specified a valid operator (+ - * /) as the second parameter"
} elseif ($num2 -eq "") {
write-host "You haven't specified the second number"
} else {
switch ($op) {
"+" {write-host $($num1+$num2)}
"-" {write-host $($num1-$num2)}
"*" {write-host $($num1*$num2)}
"/" {write-host $($num1/$num2)}
}
}
So now, if I don't pass any parameters to the script, I get the "You haven't specified any parameters" message.
If I only pass the first parameter, I get the "You haven't specified a valid operator (+ - * /) as the second parameter", which is good.
But if I pass the first parameter and, as the second parameter I pass either "+", "-", "*" or "/", I still get the "You haven't specified a valid operator (+ - * /) as the second parameter" message so there's something wrong in the first elseif statement, although it seems to be syntactically-correct.
Use Advanced Function Parameter Options
The solution mjolinor is suggesting (in his comment) would look something like this:
[CmdletBinding()]
param(
[Parameter(
Mandatory=$true
)]
[double]
$num1,
[Parameter(
Mandatory=$true
)]
[ValidateSet('+', '-', '*', '/')]
[string]
$op,
[Parameter(
Mandatory=$true
)]
[double]
$num2
)
switch ($op) {
"+" {write-host $($num1+$num2)}
"-" {write-host $($num1-$num2)}
"*" {write-host $($num1*$num2)}
"/" {write-host $($num1/$num2)}
}
This has the added benefit of letting you tab complete the operator.
Your Solution
The reason yours isn't working is because you're using -or when you should be using -and:
elseif (($op -ne "+") -and ($op -ne "-") -and ($op -ne "*") -and ($op -ne "/"))
It can get confusing because of the fact that you're checking for the negative condition (i.e. if (an_invalid_value)) where the conditions inside are negated (-ne).
Consider what happens in your version if you choose minus -: You evaluate $op -ne '+' and this is true, so condition satisfied, nothing else is tested, you go right to telling the user they did the wrong thing. With -and, all of the -ne conditions would need to be satisfied.
Here's an even simpler way of doing it your way:
elseif ( ('+','-','*','/') -notcontains $op)
Basically you say "if this array of operators does not contain $op, then display the error." It's much easier to follow.
But what's really much easier, and safer, and maintainable, is to use the built-in parameter validation options. Learn them; they're great!
Think about your condition statement:
elseif (($op -ne "+") -or ($op -ne "-") -or ($op -ne "*") -or ($op -ne "/"))
The -or means if any of them are true, then the whole test is true.
What you want is for the whole test to be true only if all of them are true.
What can you do to fix that?