are validation parameters really useful? - powershell

I'm studying powershell functions and its validate parameters. However I can't understand if they are really useful.
I make a simple example.
function get-choice(
[parameter(Mandatory=$true)][String][ValidateSet('Y','N')]$choice
)
{return $choice}
get-choice k
This function returns me this error:
get-choice : Impossibile convalidare l'argomento sul parametro 'choice'. L'argomento "k" non appartiene al set "Y,N" specificato dall'attributo ValidateSet. Fornire un argomento inclu
so nel set ed eseguire di nuovo il comando.
In riga:6 car:11
+ get-choice <<<< k
+ CategoryInfo : InvalidData: (:) [get-choice], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,get-choice
If I don't specify a valid set of values I can check them within my code:
function get-choice2(
[parameter(Mandatory=$true)][String]$choice
) {
if($choice -ne 'y' -and $choice -ne 'n') {
write-host "you must choose between yes and no"
return
}
return $choice
}
get-choice2 k
and I get a more friendly message:
you must choose between yes and no
First of all I'd like to know if it's possible to customize error message using validateset. Then I hope that someone could explain why I'd have to prefer first approach. Thanks in advance.

Some reasons why to use standard validation:
declarative code; much easier to read then the if statement
much shorter (4 lines of code compared to 1 line with only return statement)
the custom code can have some bugs
later in Vx of PowerShell there could be some custom validation messages (?) (just dreaming)
...
Check Better error messages for PowerShell ValidatePattern - post by #jaykul. You will see how you could customize the error message. It's a little bit developer oriented, but worth reading it.

The advantage to using parameter validation is that you don't have to do it yourself. That's a lot of boring, boilerplate code that no longer needs to be written and tested. A big win in my book, even though it results in less-friendly error messages.
You can write some help documentation for your function, so that user's can type help get-choice2, and see an explanation for what that parameter means:
function get-choice(
[parameter(Mandatory=$true)][String][ValidateSet('Y','N')]$choice
)
{
<#
.SYNOPSIS
Gets the user's choice.
.PARAMETER choice
The choice. Must be Y or N.
#>
return $choice
}
Run help about_comment_based_help for more details, or see the MSDN documentation.

Related

Cannot get values from Lark AST

I cannot and do not know how to retrieve the values of an AST that I generated using the Lark parser.
My grammar is as follows, saved in a .lark file :
start: (un_handle ": ")? AMOUNT "|" p_handle ("," p_handle)* (" \"" MESSAGE* "\"")?
AMOUNT: /[0-9]+(\.[0-9][0-9]?)?/
un_handle: HANDLE
p_handle: HANDLE
HANDLE : /[A-Z][A-Z]/
MESSAGE : /[^"]+/
I then run:
testText = '10|GP "Bananas"'
testTree = parser.parse(testText)
and get:
Tree(start, [Token(AMOUNT, '10'), Tree(p_handle, [Token(HANDLE, 'GP')]), Token(MESSAGE, 'Bananas')])
But, what now?
I realize that I have to probably have to build a transformer, but what methods should I define and what should I call them? I just want to extract the values for AMOUNT, un_handle, p_handle (there may be more than one p_handle), and message into Python variables.
Thank you so much in advance! Have been debugging for hours.
First off, try adding a "line" rule to provide a reference point. Yes, your application does not probably use multiple lines, but it is usually good to include one just in case.
Now, write a subroutine to find each "line" token in the AST, and append it to a list.
Finally, I suggest that you process the resulting list using a subroutine based upon the eval() subroutine in LisPy.

Removing Users from MSOL Groups

I am trying to remove all disabled users from my MSOL groups within the company. There are roughly 50 users and I have already removed them from all the DLs an Shared Mailboxes, but I still need to have them taken off of the MSOL groups. I have written something fairly simple to do so;
$import = Import-Csv "C:\Users\Person\Desktop\DisabledMSOL.csv"
foreach($user in $import) {
$DisabledUserParams = #{
PersonID = $user.GroupID
ObjectId = $user.ObjectId
}
Remove-MsolGroupMember -GroupObjectId $DisabledUserParams.ObjectId -GroupMemberType User -GroupmemberObjectId $DisabledUserParams.PersonID}
Problem is when I run this, it gives this error:
Remove-MsolGroupMember : Cannot bind parameter 'GroupMemberObjectId'. Cannot convert value "" to type "System.Guid"
Error: "Unrecognized Guid format."
At line:11 char:111
+ ... oupMemberType User -GroupmemberObjectId $DisabledUserParams.PersonID}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Remove-MsolGroupMember],
ParameterBindingException
+ FullyQualifiedErrorId :CannotConvertArgumentNoMessage,Microsoft.Online.Administration.Automation.RemoveGroupMember
Please let me know what you think.
*Also, How do you make the yellow outline at this site? I have never really found out how to do so.
Thank you.
If you have about 50 records in the csv file and you get about 50 errors when you try to run your script, I'd double check those column/property names
If you try to access a property that doesn't exist, Powershell will silently continue.
If you get a partial success (some records work others don't), I'd check the values in the csv for the failures. You may think you have a ObjectId but may not.
Lastly, check your delimiter. If the csv file is tab or pipe delimited, you'll need to specify that delimiter in the Import-Csv call.
Good Luck!
The reason why it did not work was because there was an issue of misunderstanding the cmndlets in Exchange online and the syntax that is tied with it. I do not have an on Prem server and we do all Exchange online. Despite it correctly grabbing the GUIDs from the CSV, it was the wrong syntax:
Remove-MsolGroupMember -GroupObjectId $DisabledUserParams.ObjectId -GroupMemberType User -GroupmemberObjectId $DisabledUserParams.PersonID}
The correct Syntax was:
Remove-RecipientPermission $DisabledUserParams.ObjectId -Trustee $DisabledUserParams.PersonID -AccessRights SendAs
For some reason the MSOL group came up as a mail group with only send as access; furthermore, it will only remove/add users using that syntax
Remove-RecipientPermission
Thank you for all your help though. I appreciate all the advice I get from this site.

Powershell returns wrong result

I came across this weird issue in Powershell (not in other languages). Could anyone please explain to me why this happened?
I tried to return a specified number (number 8), but the function keeps throwing everything at me. Is that a bug or by design?
Function GetNum() {
Return 10
}
Function Main() {
$Number10 = GetNum
$number10 #1 WHY NO OUTPUT HERE ??????? I don't want to use write host
$result = 8 # I WANT THIS NUMBER ONLY
PAUSE
return $result
}
do {
$again = Main
Write-Host "RESULT IS "$again # Weird Result, I only want Number 8
} While ($again -eq 10) # As the result is wrong, it loops forever
Is that a bug or by design?
By design. In PowerShell, cmdlets can return a stream of objects, much like using yield return in C# to return an IEnumerable collection.
The return keyword is not required for output values to be returned, it simply exits (or returns from) the current scope.
From Get-Help about_Return (emphasis added):
The Return keyword exits a function, script, or script block. It can be
used to exit a scope at a specific point, to return a value, or to indicate
that the end of the scope has been reached.
Users who are familiar with languages like C or C# might want to use the
Return keyword to make the logic of leaving a scope explicit.
In Windows PowerShell, the results of each statement are returned as
output, even without a statement that contains the Return keyword.
Languages like C or C# return only the value or values that are specified
by the Return keyword.
Mathias is spot on as usual.
I want to address this comment in your code:
$number10 #1 WHY NO OUTPUT HERE ??????? I don't want to use write host
Why don't you want to use Write-Host? Is it because you may have come across this very popular post from PowerShell's creator with the provocative title Write-Host Considered Harmful?
If so, I encourage you to read what I think is a great follow-up/companion piece by tby, titled Is Write-Host Really Harmful?
With this information, it should be clear that as Mathias said, you are returning objects to the pipeline, but you should also be armed with the information needed to choose an alternative, whether it's Write-Verbose, Write-Debug, or even Write-Host.
If I were going to be opinionated about it, I would go with Write-Verbose, altering your function definition slightly in order to support it:
function Main {
[CmdletBinding()]
param()
$Number10 = GetNum
Write-Verbose -Message $number10
$result = 8 # I WANT THIS NUMBER ONLY
PAUSE
$result
}
When you invoke it by just calling $again = Main you'll see nothing on the screen, and $again will have a value of 8. However if you call it this way:
$again = Main -Verbose
then $again will still have the value of 8, but on the screen you'll see:
VERBOSE: 10
likely in differently colored text.
What that gives is not only a way to show the value, but a way for the caller to control whether they see the value or not, without changing the return value of the function.
To drive some of the points in the articles home further, consider that it's not necessarily necessary to invoke your function with -Verbose to get that.
For example, let's say you stored that whole script in a file called FeelingNum.ps1.
If, in addition to the changes I made above, you also add the following to the very top of your file:
[CmdletBinding()]
param()
Then, you still invoked your function "normally" as $again = Main, you could still get the verbose output by invoking your script with -Verbose:
powershell.exe -File FeelingNum.ps1 -Verbose
What happens there is that using the -Verbose parameter sets a variable called $VerbosePreference, and that gets inherited on each function called down the stack (unless it's overridden). You can also set $VerbosePreference manually.
So what you get by using these built-in features is a lot of flexibility, both for you as the author and for anyone who uses your code, which is a good thing even if the only person using it is you.

Parameters issue in script

Can someone tell what I am doing wrong in the below I wrote:
function set-harden {
[CmdletBinding(DefaultParameterSetName='NormalHardening')]
param (
[Parameter(ParameterSetName='DoNotRemoveFromDomain')]
[Parameter(ParameterSetName='PermitHTTP' ,Mandatory=$True)]
[Parameter(ParameterSetName='PermitHTTPS' ,Mandatory=$True)]
[switch]$DONOTRemovefromdomain,
[Parameter(ParameterSetName='PermitHTTP')]
[Parameter(ParameterSetName='DoNotRemoveFromDomain')]
[switch]$Permithttp,
[Parameter(ParameterSetName='PermitHTTPS')]
[Parameter(ParameterSetName='DoNotRemoveFromDomain')]
[switch]$Permithttps,
[Parameter(ParameterSetName='NormalHardening')]
$NormalHardening
)}
If($NormalHardening -eq ""){
Write-Host "Excellent!"
}
All I want to do is to let the user select -DONOTRemovefromdomain or -Permithttp or even -Permithttps. There could be a variety of options the user has to choose from.
When I run this below I get an error:
PS C:\Temp> set-harden -DONOTRemovefromdomain -Permithttp
set-harden : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ set-harden -DONOTRemovefromdomain -Permithttp
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [set-harden], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,set-harden
Also, if I do not specify anything (so it should just go to the parameter NormalHardening) I get an nothing back:
PS C:\Temp> set-harden
PS C:\Temp>
You've specified two flags, DONOTRemovefromDomain and Permithttp that belong to two parameter sets, DoNotRemoveFromDomain and PermitHttp. The command parser has no way of knowing which parameter set you mean, so you get an error.
The reason you don't get an error when you don't specify anything is because you've set the default parameter set explicitly to NormalHardening. You've not set the Mandatory flag on the single parameter in this parameter set, and by default parameters are not mandatory so you're not seeing an error.
Instead of having all these parameter sets why not just have 2, one for the default and one for all the flags you want to set:
function set-harden {
[CmdletBinding(DefaultParameterSetName='NormalHardening')]
param (
[Parameter(ParameterSetName='Options')]
[switch]$DONOTRemovefromdomain,
[Parameter(ParameterSetName='Options')]
[switch]$Permithttp,
[Parameter(ParameterSetName='Options')]
[switch]$Permithttps,
[Parameter(ParameterSetName='NormalHardening')]
$NormalHardening
)}
If($PSCmdlet.ParameterSetName -eq "Options"){
Write-Host "Excellent!"
}
How, if the parameter set name is set to Options you can check and apply the flags. If it's set to NormalHarding then you know to use the $NormalHardening parameter.
Sean gave a good answer already about what's going on in your specific case, but I want to include some tips for troubleshooting parameter sets.
Get Help
Or more specifically, Get-Help. The parameter set syntax is automatically generated from the param block, so running Get-Help myFunction will show you how PowerShell is interpreting your parameter sets (how many, which parameters are mandatory or not in each set, etc.).
Trace the Call
If the sets look right but you're getting errors and aren't sure why, let PowerShell show you how it's binding parameters:
Trace-Command -Name ParameterBinding -Expression { Set-Harden -Permithttp } -PSHost
That can give you great insight on what's going on, and lead you to how you might fix that (or help you realize that you can't).

You cannot call a method on a null-valued expression - general

You create a script, it works for some time, then out of the blue it starts crashing with "You cannot call a method on a null-valued expression" or "The property 'property name' cannot be found on this object. Verify that the property exists and can be set.". What does this mean?
This is a Powershell version of "null pointer exception". This exception arises every time you attempt to query a variable that appears to be null. To determine what variable is null and where, you need to read the stack trace and the line/symbol numbers of the line in question. An example:
You cannot call a method on a null-valued expression.
At E:\temp\testsest.ps1:35 char:12
+ If($Search.value() -contains $SearchString)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Let's parse the error message. First, there is a wording that is in the title of this question. If you are about to ask a question with this wording, you will get a set of similar questions proposed by StackOverflow. But there is more in the error description. Second line shows script, line and character number of the first character of an expression that generates this exception. Here, a request is made to $Search.value() querying if it -contains $SearchString. The wavy underline separates the expression in full, although the proper way would be underlining only $Search.value(). Next, there is a CategoryInfo and FullyQualifiedErrorId, the latter saying "Invoke method on null", omitting "pointer" or "variable".
Now, let's debug the message. Here, the only method that's about to be called is value(), this means $Search is equal to null. Therefore, we need to get upwards from the line 35 of the script and find a place where a value is last assigned to the variable in question. This particular script had a query to Range.Find() which returns null if there's no match to the searched string. An excerpt:
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $true
$ExcelWorkBook = $Excel.Workbooks.Open($ExcelPath)
$ExcelWorkSheet = $Excel.WorkSheets.item("$location")
$Range = $ExcelWorkSheet.Range("A1").EntireColumn
$Search = $Range.find($user) # <<< here we get null
If($Search.value() -contains $user)
So, we have found where do we receive a null.
Remedies vary, but all include checks against $null. In this case it's enough to check $Search for null, and return "Nothing found" if it is indeed null. It might be not as simple, there might be more structures that can be null, like in $a.b.c.someMethod() - here either $a, $a.b or $a.b.c is null, so you need to check all of the outcomes. There are also situations where a complex structure is returned, and is expected to have a value in a certain field, but the field is not populated, therefore trying to use that field's value will produce an exception.
The moral is: If you receive an exception speaking about "null-valued", you have not expected something to return null, and you have to add checks for null (or, in fact, any unexpected) values before attempting to use the result.