Powershell errorhandling on empty parameters - powershell

This is a small script I'm calling from an excel spreadsheet:
Param (
[Int] $ID,
[String] $server,
[String] $db,
[String] $uid,
[String] $pw,
[String] $Navn,
[String] $Path
)
Try
{
Add-Type -Path $Path
foreach($i in [BrReader2.BrReader]::Main($ID, $uid, $pw, $db, $server)){if($i.Key -eq $Navn){$Data = $i}}
[BrReader2.BrReader]::WriteToTemporaryTable($Data, $ID, $uid, $pw, $db, $server)
}
Catch
{
$ErrorMessage = $_.Exception.Message
Write-Host $ErrorMessage
Read-Host -Prompt "Press Enter to exit"
}
It works mostly pretty well, except for one little thing. If for some reason one of the parameters is entered in the wrong way, like if it's completely empty, it will fail there and not write out an error message. The console window will simply quickly appear and disappear again and the user might not realise what they've done wrong.
Giving a wrong path is fine, it will still enter the try/catch block and write a proper error message to the console. Giving NO path does not keep the console window open so the user can see what went wrong though.
I need to put the 'Param (...' part at the top of the script though, so I can't put it into the try/catch block. Is there any way I can write out an error message and keep the console window open if one of the parameters fail?
I'm not very experienced with powershell so there might be something obvious I'm missing.

If you want the parameters to be set, you can make them mandatory.
If they should contain a certain value, you can also validate them.
For example:
Param (
[Parameter(Mandatory = $true)]
[ValidateRange(10,99)]
[Int] $ID,
[Parameter(Mandatory = $true)]
[String] $server,
[Parameter(Mandatory = $true)]
[ValidateSet("sql01.contoso.local","sql02.contoso.local")]
[String] $db,
[Parameter(Mandatory = $true)]
[String] $uid,
[Parameter(Mandatory = $true)]
[String] $pw,
[Parameter(Mandatory = $true)]
[String] $Navn,
[Parameter(Mandatory = $true)]
[ValidateScript({Test-Path $_ -PathType 'Container'})]
[String] $Path
)

Related

How Do I format an 'if' conditional into a variable?

I have a function where if a -Exception parameter is present, I need to use a different if statement. Is there a way I can put in if statement conditionals into a variable?
function Set-ErrorProvider
{
Param (
[Parameter(Mandatory = $true)]
[System.Windows.Forms.ErrorProvider]$Name,
[Parameter(Mandatory = $false)]
[System.Windows.Forms.Control[]]$Controls,
[Parameter(Mandatory = $false)]
[string]$TextBoxException,
[Parameter(Mandatory = $false)]
[ValidateSet("MiddleLeft")]
[string]$Alignment,
[Parameter(Mandatory = $false)]
[string]$Message,
[Parameter(Mandatory = $false)]
[int]$Padding,
[switch]$Clear
)
# Exceptions are written in the format of an if statement condition
# e.g. -and $TextBox -ne "TextBoxAction"
if ($PSBoundParameters.ContainsKey('$TextBoxException').Equals($false))
{
$TextboxIf = $Control.Text -eq $null -or $Control.Text.Trim().Length -eq 0
}
else
{
$TextboxIf = '$Control.Text -eq $null -or $Control.Text.Trim().Length -eq 0 $Exception'
}
I'm having trouble formatting the $TextboxIf variable because I want to use it as below:
if ($TextboxIf)
The $Exception variable will hold something like "-and $Textbox.Text -ne $null" (a custom exception).
Any ideas on how to format the if conditional for the $TextBoxIf variable?

Powershell functions, parameters and arg

I write own powershell func for debug like:
function StartDebug {
param (
[PARAMETER(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$FunctionName,
[PARAMETER(Mandatory = $false)]
$OtherArg
)
try {& $FunctionName $OtherArg} catch {...} finally {...}
and use it everyway, but i need more arg after $FunctionName. is it realistic to pass many arguments in this case bec use from 0 to 10 arg. do I have to list all the arguments that can be in the parameters of the function? like:
function StartDebug {
param (
[PARAMETER(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$FunctionName,
[PARAMETER(Mandatory = $false)]
$OtherArg,
[PARAMETER(Mandatory = $false)]
$OtherArg1,
[PARAMETER(Mandatory = $false)]
$OtherArg2,
[PARAMETER(Mandatory = $false)]
$OtherArg3
)
try {& $FunctionName $OtherArg OtherArg1 OtherArg2 OtherArg3 } catch {...} finally {...}
but i dont use positional parameters in code and too many named parameters in code (~100)
Interested in any ideas about this. tnx!
The magic word is Splatting. You can provide an array or a hashtable containing your arguments to a function. The splatter is written with an #VariableName instead of the $:
function StartDebug {
param (
[PARAMETER(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$FunctionName,
[PARAMETER(Mandatory = $false)]
$OtherArg
)
try {& $FunctionName #OtherArg # Watch out for the # in the OtherArg
} catch {$_} finally {}
}
$FunctionName = 'Get-ChildItem'
$Splatter = #{
Path = 'C:\'
Filter = 'Users'
Directory = $true
}
$Splatter2 = #('c:\')
StartDebug $FunctionName $Splatter
StartDebug $FunctionName $Splatter2
However if you want to use single items as $OtherArg you will have to provide them as single element array as can be seen with $Splatter2. Or extend your function to transform single arguments in arrays automatically, but thats up to you.
I think you better run it using scriptblock:
$result = Invoke-DeepDebug { Get-ChildItem -Path 'C:\' ; Get-Service -InformationAction Continue}
And in Invoke-DeepDebug you can work with $Command.AST as deep and detailed as you want.
Function Invoke-DeepDebug {
param(
[Parameter(Mandatory=$true, Position=0)]
[Scriptblock]$Command
)
Write-Host -f Cyan "Executing " -n
Write-Host -f Magenta $Command.Ast.Extent.Text -n
Write-Host -f Yellow " ... " -n
$result = $null
try {
$result = Invoke-Command $Command -ErrorAction Stop
Write-Host -f Green "OK!"
} catch {
Write-Host -f Red "Error"
Write-Host -f Red "`t$($_.Exception.Message)"
}
return $result
}

Variables not being saved in PowerShell

When writing a function:
function Manage-fullAccess
{
Param
([Parameter(ValueFromPipelineByPropertyName=$true,
Position=0,
mandatory=$true)]
$user,
[string] [Parameter(ValueFromPipelineByPropertyName = $true,
Position = 1,
mandatory=$true)]
$mailboxName,
[string] [Parameter(Position = 2)]
$fullAccess
)
# ...
}
I am being prompted to provide values, but variables are not being saved. Probably some super rookie mistake, but I would need a hand here. Both $user and $mailboxName variables are empty
You have two problems here. The first is that you are referencing $user from the main (outer) scope, the variable defined inside the function is out of scope.
The second is that PS does something interesting when a variable is reference, but there is no such variable in scope. It quietly invents such a variable, and leaves it empty.
You are going to have to learn how to transfer values from inside the function to your main scope. That's a little long for an answer here. There is some online help about Scopes. See the link below.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes?view=powershell-7.1
Adding return $user, $mailboxName to the end of your function is one way to solve this issue.
function Manage-fullAccess
{
Param
([Parameter(ValueFromPipelineByPropertyName=$true,
Position=0,
mandatory=$true)]
$user,
[string] [Parameter(ValueFromPipelineByPropertyName = $true,
Position = 1,
mandatory=$true)]
$mailboxName,
[string] [Parameter(Position = 2)]
$fullAccess
)
return $user, $mailboxName
}

Reusable SteppablePipeline code breaks randomly

I have been writing a Powershell library which uses a lot of wrappers around one or two functions. Thanks to a previous question, I discovered that I could use the GetSteppablePipeline() method to do the wrapping. However, I would like to be able to generalize this code. Googling the command, I discovered that there are only one or two samples which have been copied to death, but no single function. So I tried it. It seemed to work at first, and the sample below is roughly what I created first. But when I started to use the code in anger, it just seemed to break all over the place.
If you run the below straight through, it works ok. But if you put in any breakpoints in the ISE, it will break with errors like "Cannot find drive. A drive with the name 'function' does not exist.". If you incorporate the code into any other script, it will also break with seemingly incomprehensible errors (for instance, Where-Object not being recognised as a Cmdlet).
Function CreateSteppablePipeline
{
Param(
[Parameter(Mandatory = $true, Position = 0)]
[string]
$InnerFunctionName,
[Parameter(Mandatory = $true, Position = 1)]
[System.Collections.Generic.Dictionary`2[System.String,System.Object]]
$OuterPSBoundParameters,
[Parameter(Mandatory = $true, Position = 2)]
[System.Management.Automation.EngineIntrinsics]
$OuterExecutionContext,
[Parameter(Mandatory = $true, Position = 3)]
[System.Management.Automation.InvocationInfo]
$OuterMyInvocation
)
$outBuffer = $null
if ($OuterPSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$OuterPSBoundParameters['OutBuffer'] = 1
}
[System.Management.Automation.FunctionInfo] $wrappedCmd = $OuterExecutionContext.InvokeCommand.GetCommand($InnerFunctionName, [System.Management.Automation.CommandTypes]::Function);
[ScriptBlock] $scriptCmd = { & $wrappedCmd #OuterPSBoundParameters };
[System.Management.Automation.SteppablePipeline] $steppablePipeline = $scriptCmd.GetSteppablePipeline($OuterMyInvocation.CommandOrigin);
$steppablePipeline.Begin($OuterMyInvocation.ExpectingInput);
$steppablePipeline;
}
Function Inner
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=0)]
[string] $ExParam,
[Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
[string[]] $Value
)
Begin {
[int] $i = 0;
}
Process {
$i++;
ForEach ($myValue In $Value)
{
$myValue;
}
}
End {
$i;
$ExParam;
}
}
Function Outer
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=0)]
[string] $ExParam,
[Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
[string[]] $Value
)
Begin {
[System.Management.Automation.SteppablePipeline] $steppablePipeline = CreateSteppablePipeline "Inner" $PSBoundParameters $ExecutionContext $MyInvocation;
}
Process {
$steppablePipeline.Process($_) |
ForEach-Object {
$_ + " : Pipelined";
}
}
End {
$steppablePipeline.End()
}
}
Outer -ExParam 22 "Name1","Name2","Name3","Name4","Name5","Name6","Name7"
"Name1","Name2","Name3","Name4","Name5","Name6","Name7" | Outer -ExParam 22
It has probably to do with scoping (a problem I have come across a number of times in scripts). I tried moving parts of the CreateSteppablePipeline() function between the calling and function scope, but with no luck.

I don't understand param binding on functions with begin/process/end blocks

function Format-File {
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string] $path=$(throw "path is mandatory ($($MyInvocation.MyCommand))"),
[Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[string] $key,
[Parameter(Mandatory = $true, Position = 2, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[string] $value
)
}
I'm calling it like so, assume I've added values to the dictionary (removed for brevity here)
$dict = New-Object 'System.Collections.Generic.Dictionary[string,string]'
$dict.GetEnumerator() | Format-File -Path $updatePath
Here's my conundrum.
The above works perfectly. However, the following does not, note the difference in the key/value parameter
function Format-File {
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string] $path=$(throw "path is mandatory ($($MyInvocation.MyCommand))"),
[Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[string] $key=$(throw "key is mandatory ($($MyInvocation.MyCommand))"),
[Parameter(Mandatory = $true, Position = 2, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[string] $value=$(throw "value is mandatory ($($MyInvocation.MyCommand))")
)
}
The above throws an exception. It appears to be getting the default value when the function is first called, but when processing, the key/value parameters are set properly.
It makes a bit of sense as to why the key/value wouldn't be set at the time of the function call, but this also means my mental model is off.
So my question is two-fold.
What is the parameter binding process for functions of this nature, and
How does one verify the input of values that have come in from the pipeline? Manually check in the begin block, or is there another method?
If you have links to describe this all in greater detail, I'm happy to read up on it. It just made me realize my mental model of the process is flawed and I'm hoping to fix that.
What is the parameter binding process for functions of this nature?
In the Begin block, pipeline bound parameters will be $null or use their default value if there is one. This makes some sense, considering that the pipelining of values hasn't started yet.
In the Process block, the parameter will be the current item in the pipeline.
In the End block, the parameter will be the last value from the Process block, unless there was an exception in validating the parameter, in which case it will use the default (or $null).
How does one verify the input of values that have come in from the pipeline?
You can't check in the Begin block.
The best way is to use [Validate attributes, as you have with [ValidateNotNullOrEmpty()].
Your examples with using throw as a default value are useful in some situations but they are a clever workaround. The thing is, you don't need them since you already declared the parameter as Mandatory.
Instead of using a default value, you can use [ValidateScript( { $value -eq 'MyString' } )] for example.
Since the error message from [ValidateScript()] sucks, you can combine the techniques:
function Format-File {
param(
[Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[ValidateScript( {
($_.Length -le 10) -or $(throw "My custom exception message")
} )]
[string] $key
)
}
Using [ValidateScript()] works whether it's a pipeline parameter or not.