I have written this code
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[Int32]$BoxAlert,
[Parameter(Mandatory=$true)]
[Int32]$MailAlert
)
)
powershell.exe -WindowStyle Hidden {
if ($timeSpan.Days -ge $BoxAlert) {
drawPopupBox $result
}
if ($timeSpan.Days -ge $MailAlert) {
sendMail $result;
}
}
How to pass that $BoxAlert and $MailAlert inside the powershell.exe scriptblock?
Just need to add the -args switch after your scriptblock and a param() definition insides your script block. A simple version is
$x = bar
powershell.exe -command {param($x) write-host "foo, $x"} -args $x
Gives the following output
foo, bar
Applying this logic to your code
PowerShell.exe -WindowStyle Hidden -command {
param($BoxAlert, $MailAlert)
if($timeSpan.Days -ge $BoxAlert)
{
drawPopupBox $result
}
if($timeSpan.Days -ge $MailAlert)
{
sendMail $result;
}
} -args $BoxAlert, $MailAlert
Related
This was an unsuccessful attempt by me to explain the issue, please scroll down to see a hopefully better explanation
So I'm trying to have my script self-elevate itself to use admin rights.
I think I tried every fix possible on how to elevate a PS session to admin rights but it seems that none can make my arguments stick after the PS session re-opens. I'm assuming it's me who misunderstands how to do this properly.
I have a script that has only one parameter that can be passed to it. Here is the relevant block of code that includes the parameter in the script and the function that I call to elevate to admin permissions:
param (
[Parameter(Mandatory=$false)][bool]$Param1= $false
)
function openWithPriv {
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process PowerShell -Verb RunAs "-NoProfile -ExecutionPolicy Bypass -Command `"cd '$pwd'; & '$PSCommandPath';`";`"$args`"";
exit;
}
}
When I run my script and add my parameter, it just skips that function all together. For example: .\script.ps1 -Param1 $true runs the script in its' entirety but when it reaches my function, it just goes to the Default switch:
function runParam1 {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)][bool]$Param1= $false
)
switch ($Param1) {
$true {
Write-Host "script works"
}
Default {
Write-Host "script didn't run" -ForegroundColor Red
}
}
}
By the way, here is how I call all of the functions in my script, maybe I'm doing something wrong here as well?
#Run all functions
try {
openWithPriv
runParam1 -Param1 $Param1
someFunction1
someFunction2
}
catch {
Write-Host "Unknown error" -ForegroundColor Red -ErrorAction SilentlyContinue
Read-Host -Prompt "Press any key to continue"
}
What am I missing? Any help to fix this would be great :)
This is another attempt to explain, with the full script
So here is my script:
param (
[Parameter(Mandatory=$false)][bool]$param1 = $false
)
function openWithPriv {
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process PowerShell -Verb RunAs "-NoProfile -ExecutionPolicy Bypass -Command `"cd '$($PWD.Path)'; & '$PSCommandPath';`";`"$args`"";
exit;
}
}
function func1 {
try {
Write-Host "executing function number 1" -BackgroundColor Blue
}
catch {
Write-Host "Unknown error in func1" -ForegroundColor Red -ErrorAction SilentlyContinue
}
}
function func2 {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)][bool]$param1 = $false
)
switch ($param1) {
$true {
Write-Host "Executing function 2 because param1 was passed"
}
Default {
Write-Host "Skipping func2 because param1 is false" -ForegroundColor Yellow
}
}
}
function func3{
try {
Write-Host "Executing function 3"
}
catch {
Write-Host "Error, couldn't execute func3" -ForegroundColor Red -ErrorAction SilentlyContinue
}
}
#Run all functions
try {
openWithPriv
func1
func2 -param1 $param1
func3
Read-Host "Script finishd without an issue, press any key to exit"
}
catch {
Write-Host "Unknown error somewhere in the script" -ForegroundColor Red -ErrorAction SilentlyContinue
Read-Host -Prompt "Press any key to continue"
}
The issue:
When I execute the script and give it the parameter, nothing happens, it just skips that function and goes to the default switch which is to just prompt the user that the function was skipped.
Example
When I run .\test.ps1, this is the output:
When I run .\test.ps1 -param1 $true
This is the output:
The output should include this text Executing function 2 because param1 was passed as it's shown in func2.
Bottom line
As you can see, because I elevate the script to use admin rights the parameter I'm passing is "lost" when it reaches that function.
Hope this was a bit more clear :)
OK I think you loos the param1 when you start an other process. The $Args are not present and not passed to the script. I splited the code a little to make it clearer
Start-Process PowerShell -Verb RunAs "
-NoProfile -ExecutionPolicy Bypass -Command `"
cd '$pwd';
& '$PSCommandPath';
`";`" # this is just the string ";"
$args # these $args are written after the script is executed
`"
";
Instead you need to pass the $param1 to the script:
Start-Process PowerShell -Verb RunAs "
-NoProfile -ExecutionPolicy Bypass -Command `"
cd '$pwd';
& '$PSCommandPath' -param1 $param1
`"
";
However since $param1 is [bool] and will only accept [bool] you get an error because you are in a string and $param1 will automically be cast to [string] = True instead of $true. To prevent this use 1 and 0 instead:
if ($param1){
$Arg = 1
}else{
$Arg = 0
}
...
Start-Process PowerShell -Verb RunAs "-NoProfile -Noexit -ExecutionPolicy Bypass -Command `"cd '$($PWD.Path)'; & '$PSCommandPath' -param1 $Arg`"";
This could be shortened to:
$Arg = [int]$param1
Is there a nice way to implement the switch parameter -AsJob in custom cmdlets, like Invoke-Command has?
The only way I thought about this is:
function Use-AsJob {
[CmdletBinding()]
[OutputType()]
param (
[Parameter(Mandatory = $true)]
[string]
$Message,
[switch]
$AsJob
)
# Wrap Script block in a variable
$myScriptBlock = {
# stuff
}
if ($AsJob) {
Invoke-Command -ScriptBlock $myScriptBlock -AsJob
}
else {
Invoke-Command -ScriptBlock $myScriptBlock
}
}
Is there a better approach? I couldn't find Microsoft docs on this, any lead helps.
If we make the following assumptions:
Command is a script function
Function does not rely on module state
Then you can use the following boilerplate for any command:
function Test-AsJob {
param(
[string]$Parameter = '123',
[switch]$AsJob
)
if ($AsJob) {
# Remove the `-AsJob` parameter, leave everything else as is
[void]$PSBoundParameters.Remove('AsJob')
# Start new job that executes a copy of this function against the remaining parameter args
return Start-Job -ScriptBlock {
param(
[string]$myFunction,
[System.Collections.IDictionary]$argTable
)
$cmd = [scriptblock]::Create($myFunction)
& $cmd #argTable
} -ArgumentList $MyInvocation.MyCommand.Definition,$PSBoundParameters
}
# here is where we execute the actual function
return "Parameter was '$Parameter'"
}
Now you can do either:
PS C:\> Test-AsJob
Parameter was '123'
PS C:\> Test-AsJob -AsJob |Receive-Job -Wait
Parameter was '123'
I've searched but did not find any answer.
The task is register one dll using Powershell ps1, followed by other lines of scripts. I don't want to be interrupted by the dialog, so added the /s parameter. But now the result information is ignored, no matter succeed or fail.
I want the result displayed in console. But how?
Launch regsvr32.exe /s with Start-Process -PassThru and inspect the ExitCode property:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s C:\path\to\your.dll" -PassThru
$regsvrp.WaitForExit(5000) # Wait (up to) 5 seconds
if($regsvrp.ExitCode -ne 0)
{
Write-Warning "regsvr32 exited with error $($regsvrp.ExitCode)"
}
Here is a more complete full powershell cmdlet with pipeline support.
function Register-Dll
{
<#
.SYNOPSIS
A function that uses the utility regsvr32.exe utility to register a file
.PARAMETER Path
The file path
.PARAMETER Unregister
when specified, unregisters instead of registers
#>
[CmdletBinding()]
param (
[ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' })]
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipeLineByPropertyName=$true)]
[Alias("FullName")]
[string[]]$Path,
[Alias("u")]
[switch]$Unregister
)
begin {
if ($Unregister)
{
$regflag = "-u "
}
else
{
$regflag = ""
}
[int]$NumFailed=0
$RegExitCodes = #{
0="SUCCESS";
1="FAIL_ARGS - Invalid Argument";
2="FAIL_OLE - OleInitialize Failed";
3="FAIL_LOAD - LoadLibrary Failed";
4="FAIL_ENTRY - GetProcAddress failed";
5="FAIL_REG - DllRegisterServer or DllUnregisterServer failed.";
}
}
process {
foreach ($p in $path)
{
try
{
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag <code>$p</code>" -Wait -NoNewWindow -PassThru
if($regsvrp.ExitCode -ne 0)
{
$NumFailed++
Write-Error "regsvr32 $regflag for $p exited with error $($regsvrp.ExitCode) - $($RegExitCodes[$regsvrp.ExitCode])"
}
} catch {
$NumFailed++
Write-Error $_.Exception.Message
}
}
}
end {
if ($NumFailed -gt 0)
{
if ($Unregister)
{
$mode = "unregister"
}
else
{
$mode = "register"
}
Write-Error "Failed to $mode $NumFailed dll's, see previous errors for detail"
}
}
}
Usage:
function Register-MyAppDll
{
param(
[Parameter(Mandatory=$true,ParameterSetName="Both")]
[switch]$ReRegister,
[Parameter(Mandatory=$true,ParameterSetName="UnregisterOnly")]
[Alias("u")]
[switch]$UnRegister,
[Parameter(Mandatory=$true,ParameterSetName="RegisterOnly")]
[Alias("r")]
[switch]$Register
)
$RegOptions = #()
if ($UnRegister -or $ReRegister) { $RegOptions += #{Unregister=$true} }
if ($Register -or $ReRegister) { $RegOptions += #{} }
$dlltoregister = Get-ChildItem "C:\MyApp\bin" -Filter *.dll | where {$_ -notmatch '^interop'}
foreach ($RegOpt in $RegOptions)
{
$dlltoregister | Register-Dll #RegOpt
}
}
Register-MyAppDll -UnRegister
Register-MyAppDll -Register
Register-MyAppDll -ReRegister
Enjoy :)
Thank you Justin! I'm using this script and it works great.
There seems to be a typo in the following line of code:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag <code>$p</code>" -Wait -NoNewWindow -PassThru
The code tag shoudn't be there. I changed it to the following with added escaped double quotes around path to support spaces in paths:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag `"$p`"" -Wait -NoNewWindow -PassThru
I'm trying to create a background job, which executes a scriptblock. I need to pass this scriptblock in as a parameter, but I can't seem to get the syntax to work. The scriptblock is being converted to a string somewhere along the way.
It works fine when I pass the script block to a local function, but not through start-job
The following syntax works:
function LocalFunction
{
param (
[parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
&$ScriptBlock | % { echo "got $_" }
}
LocalFunction -ScriptBlock { echo "hello" }
This outputs "got hello" as expected.
But the following fails:
$job = start-job -argumentlist { echo "hello" } -scriptblock {
param (
[parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
&$ScriptBlock | % { echo "got $_" }
}
start-sleep -s 1
receive-job $job
The error it returns is
Receive-Job : Cannot process argument transformation on parameter 'ScriptBlock'. Cannot convert the " echo "hello" " value of type "System.String" to type "System.Management.Automation.ScriptBlock".
So if I'm reading the error right, it appears that -argumentlist is somehow forcing its arguments into strings.
Here's one way to solve this, pass the scriptblock code as a string, then create a scriptblock from the string inside the job and execute it
Start-Job -ArgumentList "write-host hello" -scriptblock {
param (
[parameter(Mandatory=$true)][string]$ScriptBlock
)
& ([scriptblock]::Create($ScriptBlock))
} | Wait-Job | Receive-Job
Looks like this works today.
function LocalFunction
{
param
(
[scriptblock] $block
)
$block.Invoke() | % { "got $_" }
}
LocalFunction { "Hello"}
Based on my experiments, PowerShell is parsing -ArgumentList, which is an Object[], as a string, even when you pass in a script block. The following code:
$job = start-job -scriptblock { $args[0].GetType().FullName } -argumentlist { echo "hello" }
start-sleep -s 1
receive-job $job
results in the following output:
System.String
As far as I know, the only solution here is Shay's, though you don't need to pass in the -ArgumentList as a string as PowerShell will parse your script block as a string in this case.
You have to read it in as a string and then convert it to a scriptblock.
In powershell v1 you can do this:
$ScriptBlock = $executioncontext.invokecommand.NewScriptBlock($string)
And in powershell v2 you can do this:
$ScriptBlock = [scriptblock]::Create($string)
So your code would look like this:
function LocalFunction
{
param (
[parameter(Mandatory=$true)]
$ScriptBlock
)
$sb = [scriptblock]::Create($ScriptBlock)
$sb | % { echo "got $_" }
}
LocalFunction -ScriptBlock "echo 'hello'"
The '[scriptblock]::Create($ScriptBlock)' will place the curly braces around the string for you creating the script block.
Found the info here http://get-powershell.com/post/2008/12/15/ConvertTo-ScriptBlock.aspx
So if your desire is to insert an inline scriptblock, then Shay's solution (as noted) is probably the best. On the other hand if you simply want to pass a scriptblock as a parameter consider using a variable of type scriptblock and then passing that as the value of the -ScriptBlock parameter.
function LocalFunction
{
param (
[parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
&$ScriptBlock | % { echo "got $_" }
}
[scriptblock]$sb = { echo "hello" }
LocalFunction -ScriptBlock $sb
There's a PowerShell script named itunesForward.ps1 that makes iTunes fast forward 30 seconds:
$iTunes = New-Object -ComObject iTunes.Application
if ($iTunes.playerstate -eq 1)
{
$iTunes.PlayerPosition = $iTunes.PlayerPosition + 30
}
It is executed with a prompt line command:
powershell.exe itunesForward.ps1
Is it possible to pass an argument from the command line and have it applied in the script instead of the hardcoded 30 seconds value?
Tested as working:
#Must be the first statement in your script (not counting comments)
param([Int32]$step=30)
$iTunes = New-Object -ComObject iTunes.Application
if ($iTunes.playerstate -eq 1)
{
$iTunes.PlayerPosition = $iTunes.PlayerPosition + $step
}
Call it with
powershell.exe -file itunesForward.ps1 -step 15
Multiple parameters syntax (comments are optional, but allowed):
<#
Script description.
Some notes.
#>
param (
# height of largest column without top bar
[int]$h = 4000,
# name of the output image
[string]$image = 'out.png'
)
And some example for advanced parameters, e.g. Mandatory:
<#
Script description.
Some notes.
#>
param (
# height of largest column without top bar
[Parameter(Mandatory=$true)]
[int]$h,
# name of the output image
[string]$image = 'out.png'
)
Write-Host "$image $h"
A default value will not work with a mandatory parameter. You can omit the =$true for advanced parameters of type boolean [Parameter(Mandatory)].
You can use also the $args variable (that's like position parameters):
$step = $args[0]
$iTunes = New-Object -ComObject iTunes.Application
if ($iTunes.playerstate -eq 1)
{
$iTunes.PlayerPosition = $iTunes.PlayerPosition + $step
}
Then it can be called like:
powershell.exe -file itunersforward.ps1 15
Call the script from a batch file (*.bat) or CMD
PowerShell Core
pwsh.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 -Param1 Hello -Param2 World"
pwsh.exe -NoLogo -ExecutionPolicy Bypass -Command "path-to-script/Script.ps1 -Param1 Hello -Param2 World"
pwsh.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 Hello -Param2 World"
pwsh.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 Hello World"
pwsh.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 -Param2 World Hello"
PowerShell
powershell.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 -Param1 Hello -Param2 World"
powershell.exe -NoLogo -ExecutionPolicy Bypass -Command "path-to-script/Script.ps1 -Param1 Hello -Param2 World"
powershell.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 Hello -Param2 World"
powershell.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 Hello World"
powershell.exe -NoLogo -ExecutionPolicy Bypass -Command "./Script.ps1 -Param2 World Hello"
Call from PowerShell
PowerShell Core or Windows PowerShell
& path-to-script/Script.ps1 -Param1 Hello -Param2 World
& ./Script.ps1 -Param1 Hello -Param2 World
Script.ps1 - Script Code
param(
[Parameter(Mandatory=$True, Position=0, ValueFromPipeline=$false)]
[System.String]
$Param1,
[Parameter(Mandatory=$True, Position=1, ValueFromPipeline=$false)]
[System.String]
$Param2
)
Write-Host $Param1
Write-Host $Param2
Let PowerShell analyze and decide the data type. It internally uses a 'Variant' for this.
And generally it does a good job...
param($x)
$iTunes = New-Object -ComObject iTunes.Application
if ($iTunes.playerstate -eq 1)
{
$iTunes.PlayerPosition = $iTunes.PlayerPosition + $x
}
Or if you need to pass multiple parameters:
param($x1, $x2)
$iTunes = New-Object -ComObject iTunes.Application
if ($iTunes.playerstate -eq 1)
{
$iTunes.PlayerPosition = $iTunes.PlayerPosition + $x1
$iTunes.<AnyProperty> = $x2
}
# ENTRY POINT MAIN()
Param(
[Parameter(Mandatory=$True)]
[String] $site,
[Parameter(Mandatory=$True)]
[String] $application,
[Parameter(Mandatory=$True)]
[String] $dir,
[Parameter(Mandatory=$True)]
[String] $applicationPool
)
# Create Web IIS Application
function ValidateWebSite ([String] $webSiteName)
{
$iisWebSite = Get-Website -Name $webSiteName
if($Null -eq $iisWebSite)
{
Write-Error -Message "Error: Web Site Name: $($webSiteName) not exists." -Category ObjectNotFound
}
else
{
return 1
}
}
# Get full path from IIS WebSite
function GetWebSiteDir ([String] $webSiteName)
{
$iisWebSite = Get-Website -Name $webSiteName
if($Null -eq $iisWebSite)
{
Write-Error -Message "Error: Web Site Name: $($webSiteName) not exists." -Category ObjectNotFound
}
else
{
return $iisWebSite.PhysicalPath
}
}
# Create Directory
function CreateDirectory([string]$fullPath)
{
$existEvaluation = Test-Path $fullPath -PathType Any
if($existEvaluation -eq $false)
{
new-item $fullPath -itemtype directory
}
return 1
}
function CreateApplicationWeb
{
Param(
[String] $WebSite,
[String] $WebSitePath,
[String] $application,
[String] $applicationPath,
[String] $applicationPool
)
$fullDir = "$($WebSitePath)\$($applicationPath)"
CreateDirectory($fullDir)
New-WebApplication -Site $WebSite -Name $application -PhysicalPath $fullDir -ApplicationPool $applicationPool -Force
}
$fullWebSiteDir = GetWebSiteDir($Site)f($null -ne $fullWebSiteDir)
{
CreateApplicationWeb -WebSite $Site -WebSitePath $fullWebSiteDir -application $application -applicationPath $dir -applicationPool $applicationPool
}
Create a PowerShell script with the following code in the file.
param([string]$path)
Get-ChildItem $path | Where-Object {$_.LinkType -eq 'SymbolicLink'} | select name, target
This creates a script with a path parameter. It will list all symbolic links within the path provided as well as the specified target of the symbolic link.
You can also define a variable directly in the PowerShell command line and then execute the script. The variable will be defined there, too. This helped me in a case where I couldn't modify a signed script.
Example:
PS C:\temp> $stepsize = 30
PS C:\temp> .\itunesForward.ps1
with iTunesForward.ps1 being
$iTunes = New-Object -ComObject iTunes.Application
if ($iTunes.playerstate -eq 1)
{
$iTunes.PlayerPosition = $iTunes.PlayerPosition + $stepsize
}