the variables can not be set by a function by dot souring - powershell

I want to implement a "Log" library in common.ps1 and then use dot souring to load it.
but it does not work as I expected I thought I can get the different value after calling SetLogConfiguratoion, but the value is not changed. then the "Log" function does not work becasue the log path is $null.
Do I miss-understand dot-sourcing?
write-host $g_nodeName ==> show $null
. 'C:\Test\Common.ps1'
write-host $g_nodeName ==> show "unkown"
SetLogConfiguratoion $sqlInstance (join-path $BackupShare 'RemvoeAgent.log')
write-host $g_nodeName ==> still "unkown"
Log 'ERROR' 'Test'
and Common.ps1 is as below
$g_logPath = $null
$g_nodeName = "Unknown"
function SetLogConfiguratoion
{
param
(
[Parameter(
Mandatory=$true,
HelpMessage='NodeName')]
[ValidateNotNullOrEmpty()]
[string]$NodeName,
[Parameter(
Mandatory=$true,
HelpMessage='LogPath')]
[ValidateNotNullOrEmpty()]
[string]$LogPath
)
if($LogPath.StartsWith('Microsoft.PowerShell.Core\FileSystem::'))
{
$g_logPath = $LogPath;
}
else
{
$g_logPath = 'Microsoft.PowerShell.Core\FileSystem::' + $LogPath;
}
$g_NodeName = $NodeName;
}
function Log
{
param
(
[Parameter(
Mandatory=$true,
HelpMessage='Log level')]
[ValidateNotNullOrEmpty()]
[ValidateSet(
'Error',
'Warning',
'Info',
'Verbose'
)]
[string]$level,
[Parameter(
Mandatory=$true,
HelpMessage='message')]
[ValidateNotNull()]
[string]$message
)
if($g_logPath -eq $null)
{
return
}
$time = Get-Date –format ‘yyyy/MM/dd HH:mm:ss’
$msg = "$time :: $level :: $nodeName :: $message"
Add-content $LogPath -value $message + '\n'
}

Dot sourcing the script will make it run in the local scope, and create the functions there, but you're still invoking the function in it's own scope. It's going to set $g_nodename in that scope. If you want all of that to run in the local scope, you need to dot source the script into the local scope to create the functions, then call the functions in the local scope (by preceding them with '. ' (note the space after the dot - that has to be there).
. SetLogConfiguratoion $sqlInstance (join-path $BackupShare 'RemvoeAgent.log')

Related

Why $Null validation failed in powershell function?

team!
I have validation script for parameter $Data.
It fail when it get $null.
whats wrong?
[CmdletBinding()]
Param (
[Parameter( HelpMessage = "PsObject data." )]
[AllowNull()]
[AllowEmptyCollection()]
[AllowEmptyString()]
[ValidateScript({
if ( ( $_ -eq $null ) -or ( $_ -eq '' ) ){
$true
}
else {
(( !( $_.GetType().IsValueType ) ) -and ( $_ -isnot [string] ))
}
})]
$Data,
...
$UserObject = Show-ColoredTable -Data $null -View 'SamAccountName', 'Name', 'DistinguishedName', 'Enabled' -Title "AD User list" -SelectMessage "Select AD user(s) to disable VPN access: " -AddRowNumbers -AddNewLine -SelectField "SamAccountName"
Most validation attributes are incompatible with [AllowNull()] because the first thing they all check - before your custom validation is invoked - is whether the input object is $null or not.
Move the validation logic inside the function body:
[CmdletBinding()]
Param (
[Parameter( HelpMessage = "PsObject data." )]
[AllowNull()]
[AllowEmptyCollection()]
[AllowEmptyString()]
$Data
)
# validate $Data here before the rest of the script/command

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
}

Same Parameter name, different scope, same value. How to deal?

# cmdlet MyCmdlet.ps1
Param(
[Parameter(Mandatory=$true)]
[string]$Variable1,
[string]$Variable2
)
Begin {
Function Function-Name {
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false)]
[string]$Variable2,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrWhiteSpace($Variable2)})]
[switch]$Choice
)
# function-body
}
}
Process {
Function-name -Variable1 "SomeString" -Choice
}
This cmdlet was called like below:
.\MyCmdlet.ps1 -variable1 "string1" -variable2 "string2"
It returns:
Cannot validate argument on parameter 'choice'. The "[string]::IsNullOrWhiteSpace($Variable2) " validation script for the argument with value "True" did not return a result of True.
It seems like the value of -Variable2 of cmdlet was implicitly passed to the function because of same variable name even without specically mentioning it during the function call inside the cmdlet.
Note: I need the variables to have same name so I can see their similar function in the future. And I use Begin, Process, End in cmdlet just so I can convert it into function and put in inside other scripts.
How can I deal with this?
By the time the ValidationScript runs, the local variable $Variable2 has not yet been created, so you get the value from $Variable2 in the parent scope.
Use $PSBoundParameters instead:
Function Function-Name {
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false)]
[string]$Variable2,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrWhiteSpace($PSBoundParameters['Variable2'])})]
[switch]$Choice
)
}
If your intention is to create a script that could be both invoked directly (as a script) and dot-sourced (so you can call the function in it from other PowerShell scripts), you could do something like this:
# Invoke-Something.ps1
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
function Invoke-Something {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
Write-Host 'invocation: function'
}
if ($MyInvocation.Line.Split()[0] -ne '.') {
Write-Host 'invocation: directly'
Invoke-Something #PSBoundParameters
} else {
Write-Host 'invocation: dot-source'
}
Using the above approach you can invoke the script directly:
PS> Invoke-Something.ps1 -Variable1 'some' -Variable2 'other'
invocation: directly
invocation: function
or dot-source it and invoke the function:
PS> . Invoke-Something.ps1
invocation: dot-source
PS> Invoke-Something -Variable1 'some' -Variable2 'other'
invocation: function
For this to work you must make all parameters of the script optional, though.
A simplified version of this would not define parameters on the script level and pass #args to the invoked function:
# Invoke-Something.ps1
function Invoke-Something {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
Write-Host 'invocation: function'
}
if ($MyInvocation.Line.Split()[0] -ne '.') {
Write-Host 'invocation: directly'
Invoke-Something #args
} else {
Write-Host 'invocation: dot-source'
}

Powershell v3 Isfilter unnamed script block.

The following code snipit works in PowerShell v2, but not v4.. In the release notes for PowerShell v3 is explains that you cannot set the IsFilter property on an unnamed script block. I believe that's exactly what I have, but I don't understand what change to make..
Any help would be appreciated.
function Stop-WindowsService
{
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
$fromPipe,
[Parameter(ParameterSetName='static',Mandatory=$true,Position=0)]
[ValidateNotNullOrEmpty()]
[string]$name,
[Parameter(ParameterSetName='dynamic',Mandatory=$true,Position=0)]
[ValidateNotNull()]
[ScriptBlock]$scriptReturningName,
[Parameter(Mandatory=$false)]
[ValidateRange(1,86400)]
[int]$timeout = 60
)
Process {
$server = $_
if($PsCmdlet.ParameterSetName -eq 'dynamic') {
$scriptReturningName.IsFilter = $true
$name = ($server | &$scriptReturningName)
}
Write-Verbose "$($server.Name): $name ==> Checking"
$service = $server | Get-WindowsServiceRaw $name

Argument list and the pipeline

as an adjunct to this issue: How do I force declared parameters to require explicit naming? I am struggling with pipelines. Suppose I want the behaviour that this declares:
param(
$installdir,
$compilemode,
[Parameter(Position=0, ValueFromRemainingArguments=$true)] $files
)
namely, that I can call my script like this:
c:\> MyScript -installdir c:\ file-1.txt file-2.txt file-3.txt
but now I want to also be able to do it this way:
c:\> gi file-*.txt |MyScript -installdir c:\
I might think of adding a decoration to the parameter like this:
param(
$installdir,
$compilemode,
[Parameter(
Position=0,
ValueFromRemainingArguments=$true,
ValueFromPipeline=$true
)] $files
)
but what actually happens is I only get 1 argument into my parameter i.e. instead of getting an array with all the files that gi produced, I get only the first in the list.
A second way I attempted this was by using the $input variable (instead of using the ValueFromPipeline decorator), but then in trying to call the script I get the error:
The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input
and its properties do not match any of the parameters that take
pipeline input.
where can I go from here?
You could declare it without ValueFromRemainingArguments:
param(
[Parameter(
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
$files,
$installdir,
$compilemode
)
And then pass in multiple files as an array using the comma operator e.g.:
MyScript -installdir c:\ file-1.txt,file-2.txt,file-3.txt
Note: In order to accept input from commands like Get-Item and Get-ChildItem, use ValueFromPipelineByPropertyName and add a parameter alias "PSPath" that will find the PSPath property on the objects output by Get-Item/Get-ChildItem.
I have test this in ISE and it works fine:
function foo
{
param(
[Parameter(
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
$files,
$installdir,
$compilemode
)
process {
foreach ($file in $files) {
"File is $file, installdir: $installdir, compilemode: $compilemode"
}
}
}
foo a,b,c -installdir c:\temp -compilemode x64
ls $home -file | foo -installdir c:\bin -compilemode x86
FYI, this is a template I use all the time to create commands that take pipeline input or array input, as well as wildcard paths:
function Verb-PathLiteralPath
{
[CmdletBinding(DefaultParameterSetName="Path",
SupportsShouldProcess=$true)]
#[OutputType([output_type_here])] # Uncomment this line and specify the output type of this
# function to enable Intellisense for its output.
param(
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="Path",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to one or more locations.")]
[ValidateNotNullOrEmpty()]
[SupportsWildcards()]
[string[]]
$Path,
[Alias("PSPath")]
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="LiteralPath",
ValueFromPipelineByPropertyName=$true,
HelpMessage="Literal path to one or more locations.")]
[ValidateNotNullOrEmpty()]
[string[]]
$LiteralPath
)
Begin
{
Set-StrictMode -Version Latest
}
Process
{
if ($psCmdlet.ParameterSetName -eq "Path")
{
if (!(Test-Path $Path)) {
$ex = new-object System.Management.Automation.ItemNotFoundException "Cannot find path '$Path' because it does not exist."
$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
$errRecord = new-object System.Management.Automation.ErrorRecord $ex, "PathNotFound", $category, $Path
$psCmdlet.WriteError($errRecord)
}
# In the -Path (non-literal) case, resolve any wildcards in path
$resolvedPaths = $Path | Resolve-Path | Convert-Path
}
else
{
if (!(Test-Path $LiteralPath)) {
$ex = new-object System.Management.Automation.ItemNotFoundException "Cannot find path '$LiteralPath' because it does not exist."
$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
$errRecord = new-object System.Management.Automation.ErrorRecord $ex, "PathNotFound", $category, $LiteralPath
$psCmdlet.WriteError($errRecord)
}
# Must be -LiteralPath
$resolvedPaths = $LiteralPath | Convert-Path
}
foreach ($rpath in $resolvedPaths)
{
if ($pscmdlet.ShouldProcess($rpath, "Operation"))
{
# .. process rpath
}
}
}
End
{
}
}