Printing the argument in powershell - powershell

May I know, why the string argument is not printed in the following powershell script?
function Get-Name ( [string] $Username ) {
echo "user : $Username"
}
PS C:\> .\Get-Name.ps1 -username "test"
PS C:\>

The script file Get-Name.ps1 only defines the function Get-Name, it doesn't execute it.
Use the dot-source operator (.) to have it define the function in the calling scope, and then you can execute the function itself:
PS C:\> . .\Get-Name.ps1
PS C:\> Get-Name -Username test
user : test
Alternatively, remove the function Get-Name { and } part of the script as pointed out by Lee_Dailey, at which point the script file becomes a parameterized function in itself and you can then do:
PS C:\> .\Get-Name.ps1 -Username test
user : test
Please see the about_Scripts help file, especially the section about script scope and dot sourcing

the problem is that you define a function, not a callable script. [grin] this will work ...
Param ([string] $Username)
echo "user : $Username"
here's an example of calling the above ...
. .\Func_Get-Name.ps1 -username 'tutu'
output ...
user : tutu

Related

Start-Job: Call another functions in a script

After reading a lot of Q&A here on SO about Start-Job I am still can not understand what I am doing wrong...
The main idea: I need to run a lot of functions that call another functions with different parameters. Something like this:
function Base-Function {
PARAM(
[string]
$Param
)
# I will do something with $Param
}
function Copy-File {
PARAM(
[string]
$CopyFileParam
)
Base-Function -Param $CopyFileParam
}
function Read-File {
PARAM(
[string]
$ReadFileParam
)
Base-Function -Param $ReadFileParam
}
function Move-File {
PARAM(
[string]
$MoveFileParam
)
Base-Function -Param $MoveFileParam
}
So - I am trying to call Copy-File, Read-File and Move-File simultaneously:
function Main-Function {
$copyFileArgs = #{ "CopyFileParam" = 1 }
Start-Job -ScriptBlock ${Function:Copy-File} -ArgumentList $copyFileArgs
$readFileArgs = #{ "ReadFileParam" = 2 }
Start-Job -ScriptBlock ${Function:Read-File} -ArgumentList $readFileArgs
...
...
}
but of course I can not call Base-Function inside Copy-File function this way so I added -InitializationScript argument:
$init = {
function Base-Function {
PARAM(
[string]
$Param
)
# I will do something with $Param
}
}
and then I call it like this:
function Main-Function {
$copyFileArgs = #{ "CopyFileParam" = 1 }
Start-Job -ScriptBlock ${Function:Copy-File} -ArgumentList $copyFileArgs -InitializationScript $init
}
but then I get an error:
OpenError: [localhost] An error occurred while starting the background process. Error reported: An error occurred trying to start process 'C:\Program Files\PowerShell\7\pwsh.exe' with working directory 'C:\Projects\powershell\project'. The filename or extension is too long..
So my question is:
Any suggestion to simultaneously call different function that in they turn call to some in-script functions ?
Why I get this error The filename or extension is too long ?
Here is a link to powershell script for example: Gist
Run the script and let it finish
In the same shell window check for jobs: Get-Job
Check the output of running job: Receive-Job xxx
see that output of job is:
ObjectNotFound: The term 'Base-Function' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
ObjectNotFound: The term 'Base-Function' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Sorry for misinforming you; the code above is correct and functional (many thanks to #mklement0 for hints and suggestions).
The actual problem that I encountered was in this line:
OpenError: [localhost] An error occurred while starting the background process.
Error reported: An error occurred trying to start process 'C:\Program Files\PowerShell\7\pwsh.exe' with working directory 'C:\Projects\powershell\project'.
The filename or extension is too long..
The filename or extension is too long.. -> this means that there is a character's length limit for what can be passed in the '-InitializationScript' parameter. You could check it in Gist example above - everything work OK.
Here is Stakoverflow question that give me this idea: Max size of ScriptBlock / InitializationScript for Start-Job in PowerShell
Once I put all my code instead in -InitializationScript parameter inside ps1 script file and then dot source it inside function - everything started working perfectly.
Thanks again #mklement0

complex default values for powershell script Params

I would like to set a default Param value that must be determined at script runtime (cannot be hardcoded).
Given powershell script file.ps1
<#
.PARAMETER arg1
The first argument. Defaults to ???
#>
Param (
[string] $arg1,
)
I would like:
Set $arg1 to a value that must be determined when the script runs, for example, the host IP address.
Print the default value of $arg1 within the .PARAMETER arg1 help message.
Typically I might add
$arg1_default = (Test-Connection -ComputerName "www.google.com" -Count 1).Address.IPAddressToString
<#
.PARAMETER arg1
The first argument. Defaults to $arg1_default.
#>
Param (
[string] $arg1 = $arg1_default,
)
However, in Powershell, the Param statement must be the first processed statement. If I add any complex statements before Param statement then it results in an error:
PS> .\file.ps1
Param: file.ps1:9
Line |
9 | Param (
| ~~~~~
| The term 'Param' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the
| name, or if a path was included, verify that the path is correct and try again.
How do I set default values for a powershell script Param ?
I'm using Powershell 7.
You can use a function with [CmdletBinding()] attribute like Toto in the file test.ps1 defining a default value for a particular parameter for this CmdLet with $PSDefaultParameterValues
# Test.ps1
$PSDefaultParameterValues["Toto:arg1"]=Invoke-Command -ScriptBlock {(Test-Connection -ComputerName "www.google.com" -Count 1).IPV4Address.IPAddressToString}
Function Toto
{
[CmdletBinding()]
Param ([string] $arg1)
Write-Output $arg1
}
Now in the script where you want to use this (or these) function(s) you can first dot source the.ps1 file that contains the function(s), then call your function toto
. /Test.ps1
Toto # without argument gives "172.217.18.196" for me now
Toto titi # With argument give "titi"

Submitting parameters to Invoke-Expression

I've written a sample Powershell Script with name C:\Script\Scrip1.ps1
Below is the code
Function Testfunction(){
Param(
$Node1,
$Node2
)
$SQLNodes = #($Node1, $Node2)
foreach ($node in $SQLNodes)
{
#Some code below is dummy code
"$node" | Out-File C:\File1.txt -Append
}
}
When i try to call this function using invoke-Expression it doesn't work
Used below method with no luck
$string = 'C:\Script\Script1.ps1 Testfunction -Node1 "test" -Node2 "test2"'
Invoke-Expression $string
I have opened a PS Window and ran below command without luck
.\Script1.ps1 -Node1 Hello -Node2 Aquib
or
.\Script1.ps1 Testfunction -Node1 Hello -Node2 Aquib
I do not see any file1 under C:\File1
when when I open the script file and then run the function, it does work and generate the file.
You don't need to use Invoke-Expression in your scenario.
If you want to make Testfunction visible in the current scope, you will need to "dot-source" your script:
PS C:\> . C:\Scripts\Script1.ps1
This executes Script.ps1 in the current scope, which will define Testfunction in the current scope, and then you can run the function:
PS C:\> Testfunction -Node1 "Test1" -Node2 "Test2"
Another alternative is to skip defining Testfunction as a function in a script, and just use it as a script itself:
# Script file
param(
$Node1,
$Node2
)
$SQLNodes = #($Node1, $Node2)
foreach ($node in $SQLNodes) {
#Some code below is dummy code
"$node" | Out-File C:\File1.txt -Append
}
If you name the script Testfunction.ps1, you can run it by typing the script's name:
PS C:\> C:\Scripts\Testfunction.ps1 -Node1 "Test1" -Node2 "Test2"

Powershell answer file similar to Linux bash

What I want: Execute my powershell script (.ps1) and answer all the read-host questions using an answer file.
On Linux bash this is straightforward see:Answer file Linux bash
You can use a subexpression ($()) to call Read-Host in case a parameter argument is not present:
param(
$Name = $(Read-Host -Prompt "Input Name"),
$Company = $(Read-Host -Prompt "Input Company Name"),
$Country = $(Read-Host -Prompt "Country")
)
if(-not $Country){
$Country = "US"
}
Write-Host $(#'
Hello, {0} from {1}!
How is the weather in {2}?
'# -f $Name,$Company,$Country)
# do more stuff
Save as myscript.ps1.
Now, when you run it without any parameters, it prompts you for all the values:
PS C:\> .\prompt.ps1
Input Name: Arni
Input Company Name: MegaCorp Inc.
Country:
Hello, Arni from MegaCorp Inc.!
How is the weather in US?
PS C:\>
But if you want to run it without being prompted, just supply all required parameters:
PS C:\> .\prompt.ps1 -Name Xavier -Company "SuperCorp SA" -Country Brazil
Hello, Xavier from SuperCorp SA!
How is the weather in Brazil?
PS C:\>
What you are asking for does not exist in PowerShell, the methods of passing arguments to a function (e.g. splatting, the pipeline) cannot be used with Read-Host.
What is possible is creating your script as a function and making use of the aforementioned methods to pass the arguments when the function is to be run in an automated fashion.

Powershell Start-Process to start Powershell session and pass local variables

Is there a way to use the Powershell Start-Process cmdlet to start a new Powershell session and pass a scriptblock with local variables (once of which will be an array)?
Example:
$Array = #(1,2,3,4)
$String = "This is string number"
$Scriptblock = {$Array | ForEach-Object {Write-Host $String $_}}
Start-Process Powershell -ArgumentList "$Scriptblock"
Thanks.
I'm pretty sure there's no direct way to pass variables from one PowerShell session to another. The best you can do is some workaround, like declaring the variables in the code you pass in -ArgumentList, interpolating the values in the calling session. How you interpolate the variables into the declarations in -ArgumentList depends on what types of variables. For an array and a string you could do something like this:
$command = '<contents of your scriptblock without the curly braces>'
Start-Process powershell -ArgumentList ("`$Array = echo $Array; `$String = '$String';" + $command)
I was able to get this to work by joining the array with "/" to create a string and entering the scriptblock into another .ps1 script with appropriate parameters and splitting the joined string back to an array within the second script and using
Start-Process Powershell -ArgumentList "&C:\script.ps1 $JoinedArray $String"
Ugly, but it's the only way I could get it to work. Thanks for all the replies.
You could wrap the contents of your script block in a function, and then call the function from the ArgumentList and pass in the variables as parameters to the function, as I do on this post.
$ScriptBlock = {
function Test([string]$someParameter)
{
# Use $someParameter to do something...
}
}
# Run the script block and pass in parameters.
$myString = "Hello"
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test('$myString')}"
The command line options for PowerShell.exe say that you should be able to pass arguments when using a script block by adding -args:
PowerShell.exe -Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] }
However when I try to do that I get the following error:
-args : The term '-args' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
I added $MyInvocation | fl to the script block to see what was happening, and it looks like the -args is just appended to the deserialized commands in the script block (hence the error since -args is not a valid command). I also tried using GetNewClosure() and $Using:VariableName but those only appear to work when the script block is invoked (as opposed to this where we are using it to serialize/deserialize the commands).
The I was able to get it to work by wrapping it in a function like deadlydog's answer.
$var = "this is a test"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host "hello world: $message"
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"AdminTasks('$var')" -Verb runAs #-WindowStyle Hidden
#Output:
MyCommand :
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host hello world: $message
}
AdminTasks('this is a test')
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 0
OffsetInLine : 0
HistoryId : 1
ScriptName :
Line :
PositionMessage :
PSScriptRoot :
PSCommandPath :
InvocationName :
PipelineLength : 2
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Runspace
DisplayScriptPosition :
hello world: this is a test
Wrapping it in a script block and using $args[0] or $args[1] also works, just be aware that you many need to wrap the $var0 or $var1 in quotes if there are issues when it is deserialized and use `$ to prevent the $sb from being replaced with "" since that variable doesn't exist in the caller's scope:
$var0 = "hello"
$var1 = "world"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
$sb = {
write-host $args[0] $args[1]
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"& `$sb $var0 $var1"
If you want to pass objects that are serializable, but are not strings, I wrote up a solution: Is there a way to pass serializable objects to a PowerShell script with start-process?