Using AutoIt 3, is there a way to pass additional arguments to the callback method in the _Timer_SetTimer function?
Here's my use case (main loop) :
For $item In $items
_Timer_SetTimer(0, $timeOffset, "MyMethod")
Next
Callback method :
Func MyMethod($hWnd, $iMsg, $iTimerID, $iTime)
_Timer_KillTimer ( $hWnd, $iTimerID )
// Do something on $item
EndFunc
I tried using a Global variable, but every single instance of MyMethod then uses the last value. I did it this way :
Global $currentItem
For $item In $items
$currentItem = $item
_Timer_SetTimer(0, $timeOffset, "MyMethod")
Next
Func MyMethod($hWnd, $iMsg, $iTimerID, $iTime)
_Timer_KillTimer ( $hWnd, $iTimerID )
$item = $currentItem
// Do something on $item
EndFunc
So, am I doing it wrong or is there a way to pass argument directly? Thanks.
If your delayed calls are ordered, you could still use a Global variable to store the values in an Array :
Global $values[0]
For $item In $items
_ArrayAdd($values, $item)
_Timer_SetTimer(0, $timeOffset, "MyMethod")
Next
Func MyMethod($hWnd, $iMsg, $iTimerID, $iTime)
_Timer_KillTimer ( $hWnd, $iTimerID )
_ArrayReverse($values)
$item = _ArrayPop($values)
_ArrayReverse($values)
// Do something on $item
EndFunc
The double reverse and pop are there to simulate a FIFO queue
Related
I want to wrap a function, that is to create a new function such that it would automatically pass some arguments to the old function, like python's partial functions. The arguments passed are the ones defined in the callee and not the caller. The important thing is that I don't want to refer to each of them explicitly (define them twice).
That is really done to save typing-in the same flags to complicated functions while allowing customization.
For example, in python, I would do:
call_with_x=partial(call,x=1)
or maybe use **kw and pass it to the callee in some cases .
This is my best try (based on Wrapper function in PowerShell: Pass remaining parameters) :
function Let
{
[CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$Option,
[parameter(mandatory=$false, position=1, ValueFromRemainingArguments=$true)]$Remaining)
Get #Remaining
}
function Get
{
[CmdletBinding()]
Param([parameter(mandatory=$false, position=0)][string]$OptionA,
[parameter(mandatory=$true, position=1)][string]$OptionB)
Write-Host $OptionA, $OptionB
}
But Let -Option c -OptionA 1
Prints -OptionA 1
which is obviously not what I intended.
If you don't require "advanced" function features from CmdletBinding(), then you can get away with using $args for this:
# simple function parameters are positional and named, but not mandatory
function let {
Param($OptionA,$OptionB)
write-host "OptionA=$OptionA"
write-host "OptionB=$OptionB"
get #args
}
function get {
Param($OptionC,$OptionD)
write-host "OptionC=$OptionC"
write-host "OptionD=$OptionD"
}
# not necessary to name or include parameters
let -OptionA A B -OptionC C D
OptionA=A
OptionB=B
OptionC=C
OptionD=D
# named parameters get assigned first, so the order is not too big a deal either
# these produce the same results as above:
let B -OptionA A D -OptionC C
let -OptionD D -OptionC C A B
Any named parameters will not get positionally assigned to let
Any additional parameters, named or otherwise, will be forwarded to get
That was hard!
For this you would need to add a bit of code in DynmaicParams and begin section of the function.
It might be possible to do it in an Attribute
function x{
[WrapperFor(y)]
Might do it Later.
function Get
{
[CmdLetBinding()]
Param([parameter(mandatory=$false, position=0)][string]$OptionA,
[parameter(mandatory=$false, position=1)][string]$OptionB)
Write-Host "opta",$OptionA
Write-Host "optb",$OptionB
}
function Let
{
[CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$Option,[parameter(mandatory=$false, position=0)][string]$OptionB)
DynamicParam {
AddWrapper -For "Get" -To "Let"
}
Begin {
$params = GetRestOfParams "Get" $PSBoundParameters
}
Process {
Get #params
}
}
Needed code:
using namespace System.Management.Automation
function Empt
{
[CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$aaaa)
1
}
function AddWrapper([parameter(mandatory=$true, position=0)][string]$For,[parameter(mandatory=$true, position=1)][string]$To)
{
$paramDictionary = [RuntimeDefinedParameterDictionary]::new()
$paramset= $(Get-Command $For).Parameters.Values | %{[System.Management.Automation.RuntimeDefinedParameter]::new($_.Name,$_.ParameterType,$_.Attributes)}
$paramsetlet= $(Get-Command empt).Parameters.Keys
$paramsetlet+= $(Get-Command $To).ScriptBlock.Ast.Body.ParamBlock.Parameters.Name | %{ $_.VariablePath.UserPath }
$paramset | %{ if ( -not ($paramsetlet -contains $_.Name) ) {$paramDictionary.Add($_.Name,$_)}}
return $paramDictionary
}
function GetRestOfParams($dst,$params)
{
$dstorgparams=$(Get-Command $dst).Parameters.Keys
$z= $params
$z.Keys | %{ if ( -not ($dstorgparams -contains $_) ) {$z.Remove($_)} } | Out-Null
return $z
}
I am writing a PowerShell module, the functions inside this module have some parameters which will be re-used across all functions. Rather than copy-pasting the function definition each time I add a new function, I would like to define them at the top like a script variable and then insert them into each function, giving me a single place to update if they need to be changed.
Looking at how dynamic parameters are defined it seems like I should be able to define an object of that type and then reference it in the function definitions, but I can't find anything online giving me the correct syntax to do this.
Using PowerShell version 7.2
$Script:ReUsedParameters = param(
[Parameter()]
[String]$Name,
[Parameter()]
[Int]$Id
)
Function New-Command {
Param ($ReUsedParameters)
Write-Output "Name: $Name, ID: $ID"
}
For the sake of answering, you can store the runtime parameters definitions in a script block and then call it & inside the function's dynamicparam block.
I do not think this is a good idea nor I recommend using this. All functions should have their own repeated param blocks if needed.
$reusedParameters = {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
# Since both parameters don't have any arguments (Mandatory, Position, ValueFromPipeline, etc..)
# you can use this one for both, otherwise, each dynamic parameter should have their own
# Parameter Declaration
[Parameter[]] $paramAttribute = [Parameter]::new()
$paramDictionary['Name'] = [System.Management.Automation.RuntimeDefinedParameter]::new('Name', [string], $paramAttribute)
$paramDictionary['Id'] = [System.Management.Automation.RuntimeDefinedParameter]::new('Id', [int], $paramAttribute)
return $paramDictionary
}
Function New-Command {
[CmdletBinding()] # `CmdletBinding` is Mandataroy here
param() # if the `param` block is empty
dynamicparam {
& $reusedParameters
}
end {
# Caveat: you can reference these parameters via $PSBoundParameters
# $Name and $Id are not part of the `param` block
# hence that wouldn't work here
"Name: {0}, ID: {1}" -f $PSBoundParameters['Name'], $PSBoundParameters['ID']
}
}
New-Command -Name asd -Id 123
As a declarative approach, you may turn the common parameters into class properties and have a single function parameter of the class type.
class MyReUsedParameters {
[String] $Name
[Int] $Id = 23
}
Function New-Command {
Param (
[MyReUsedParameters] $ReUsedParameters,
$AnotherParam
)
Write-Output "Name: $($ReUsedParameters.Name), ID: $($ReUsedParameters.ID)"
}
# Pass the common parameters as a hashtable which gets converted to
# MyReUsedParameters automatically.
New-Command -ReUsedParameters #{ Name = 'foo'; Id = 42 } -AnotherParam bar
# Alternatively pass the common parameters as a (typed) variable.
# PowerShell is able to deduce the argument name from the type.
$commonArgs = [MyReUsedParameters] #{ Name = 'Foo'; Id = 42 }
New-Command $commonArgs -AnotherParam bar
When passing a hashtable or PSCustomObject that has matching properties, it will automatically be converted to the class type.
You may even validate class properties similar to regular parameters. Most parameter validation attributes can be specified for class properties as well.
class MyReUsedParameters {
[ValidateNotNullOrEmpty()] [String] $Name
[Int] $Id = 23
# Constructor - required to apply validation
MyReUsedParameters( [Hashtable] $ht ) {
$this.Name = $ht.Name
$this.Id = $ht.Id
}
}
Function New-Command {
Param (
[Parameter(Mandatory)]
[MyReUsedParameters] $ReUsedParameters
)
Write-Output "Name: $($ReUsedParameters.Name), ID: $($ReUsedParameters.ID)"
}
# Causes an error (as expected), because Name property is missing
New-Command -ReUsedParameters #{ Id = 42 }
Consider two powershell functions:
function A {
params(
[Parameter()]$x,
[Parameter()]$y
)
Write-Host $x $y
}
function B {
params(
[Parameter()]$x,
[Parameter()]$z
)
Write-Host $x $z
}
I'd like to define a single parameter $x once (which could have fairly complex attributes that must be kept identical for both functions) and re-use it in both functions, so something like:
$x = {[Parameter()]$x}
function A {
params(
$x,
[Parameter()]$y
)
Write-Host $x $y
}
function B {
params(
$x,
[Parameter()]$z
)
Write-Host $x $z
}
(How) is this possible?
To reuse parameter declarations across functions - as requested in your question - see the following section.
To reuse parameter values (arguments) across functions, by way of presets (default value), see the bottom section.
In order to reuse parameter declarations - short of using design-time templating to generate source code - you need to to define a script block that creates a dynamic parameter that can be passed to the dynamicparam block of multiple advanced functions:
using namespace System.Management.Automation
# The script block that declares the dynamic parameter to be shared
# across functions.
$sharedDynParam = {
# Define the -x parameter dynamically.
$paramName = 'x'
$dict = [RuntimeDefinedParameterDictionary]::new()
$dict.Add(
$paramName,
[RuntimeDefinedParameter]::new(
$paramName,
[datetime], # Type the parameter [datetime]. for instance.
[ParameterAttribute] #{
Mandatory = $true # Make the parameter mandatory, for instance.
# ParameterSetName = 'default' # Assign it to a parameter set, if neeeded.
}
)
)
# Return the dictionary
return $dict
}
function A {
[CmdletBinding()]
param(
$y
)
# Assign the shared dynamic parameter.
dynamicparam { & $sharedDynParam }
# The use of `dynamicparam { ... }` requires use of an explicit
# `process { ... }` block (and optionally `begin { ... }` and
# `end { ... }`, as needed).
process {
# Note: A dynamic -x parameter cannot be accessed as $x
# Instead, it must be accessed via the $PSBoundParameters dictionary.
"[$($PSBoundParameters['x'])] - [$y]"
}
}
function B {
[CmdletBinding()]
param(
$z
)
# Assign the shared dynamic parameter.
dynamicparam { & $sharedDynParam }
process {
"[$($PSBoundParameters['x'])] - [$z]"
}
}
# Sample calls,
A -x '1970-01-01' -y yval
B -x '1970-01-02' -z zval
Output:
[01/01/1970 00:00:00] - [yval]
[01/02/1970 00:00:00] - [zval]
To preset the value of a parameter by a given name across commands, use the $PSDefaultParameterValues preference variable:
# Preset a parameter value for all commands ('*') that have
# an -x ('x') parameter.
$PSDefaultParameterValues = #{ '*:x' = [pscustomobject] #{ foo = 1; bar = 2 } }
function A {
[CmdletBinding()] # This makes the function an *advanced* one, which respects
# $PSDefaultParameterValues; similarly, at least one
# parameter-individual [Parameter()] attribute does the same.
param(
$x,
$y
)
"[$x] - [$y]"
}
function B {
[CmdletBinding()]
param(
$x,
$z
)
"[$x] - [$z]"
}
# Sample calls, without an -x argument, relying on
# $PSDefaultParameterValues to provide it automatically.
A -y yval
B -z zval
Output, showing that parameter -x was automatically bound via $PSDefaultParameterValues:
[#{foo=1; bar=2}] - [yval]
[#{foo=1; bar=2}] - [zval]
Let's say I have array Grid that was initialized with
$Grid = #(#(1..3), #(4..6), #(7..9))
and I want to change Grid[0][0] to value "Test" but I want to make it unchangeable, is there a way I could to that?
So far I've tried playing around with classes that allow read-only or constant declarations through the class as opposed to using New-Variable/Set-Variable but it doesn't affect the index itself but the individual element as in
$Grid[0][0] = [Array]::AsReadOnly(#(1,2,3))
$Grid[0][0] # 1 \n 2 \n 3
$Grid[0][0].IsReadOnly # True
$Grid[0][0] = "test"
$Grid[0][0] # test
I assume this is due to $Grid[0][0] being read-only as opposed to constant and the behaviour I experienced supported that:
$test = [Array]::AsReadOnly(#(1,2,3,4))
$test[0]=1 # Errors
$test = "test"
$test # test
$Grid = #(#(1..3), #(4..6), #(7..9))
$Grid[0][0] = [Array]::AsReadOnly(#(1,2,3))
$Grid[0][0][0] = 1 # Errors
$Grid[0][0] = "test"
$Grid[0][0] # test
I'm not sure what to try next and I know that this is very simple with classes but I am not looking for that as a solution.
You'll have to make both dimensions of your nested array read-only to prevent anyone from overwriting $grid[0]:
$grid =
[array]::AsReadOnly(#(
,[array]::AsReadOnly(#(1,2,3))
,[array]::AsReadOnly(#(3,2,1))
))
(the unary , is not a typo, it prevents PowerShell from "flattening" the resulting read-only collection)
Now $grid should behave as you expect:
$grid[0] # 1,2,3
$grid[0][0] # 1
$grid[0][0] = 4 # error
$grid[0] = 4 # error
If you want to be able to prevent writing to individual "cells", you'll have to define a custom type:
using namespace System.Collections
class Grid {
hidden [int[,]] $data
hidden [bool[,]] $mask
Grid([int]$width,[int]$height){
$this.mask = [bool[,]]::new($width, $height)
$this.data = [int[,]]::new($width, $height)
}
[int]
Get([int]$x,[int]$y)
{
if(-not $this.CheckBounds($x,$y)){
throw [System.ArgumentOutOfRangeException]::new()
}
return $this.data[$x,$y]
}
Set([int]$x,[int]$y,[int]$value)
{
if(-not $this.CheckBounds($x,$y)){
throw [System.ArgumentOutOfRangeException]::new()
}
if(-not $this.mask[$x,$y])
{
$this.data[$x,$y] = $value
}
else
{
throw [System.InvalidOperationException]::new("Cell [$x,$y] is currently frozen")
}
}
Freeze([int]$x,[int]$y)
{
if(-not $this.CheckBounds($x,$y)){
throw [System.ArgumentOutOfRangeException]::new()
}
$this.mask[$x,$y] = $true
}
Unfreeze([int]$x,$y)
{
if(-not $this.CheckBounds($x,$y)){
throw [System.ArgumentOutOfRangeException]::new()
}
$this.mask[$x,$y] = $false
}
hidden [bool]
CheckBounds([int]$x,[int]$y)
{
return (
$x -ge $this.data.GetLowerBound(0) -and
$x -le $this.data.GetUpperBound(0) -and
$y -ge $this.data.GetLowerBound(1) -and
$y -le $this.data.GetUpperBound(1)
)
}
}
Now you can do:
$grid = [Grid]::new(5,5)
$grid.Set(0, 0, 1) # Set cell value
$grid.Get(0, 0) # Get cell value
$grid.Freeze(0, 0) # Now freeze cell 0,0
$grid.Set(0, 0, 2) # ... and this will now throw an exception
$grid.Set(0, 1, 1) # Setting any other cell still works
If you want native support for index expressions (ie. $grid[0,0]), the Grid class will need to implement System.Collections.IList
I am a newbie to the world of programming and I am trying to create a form using functions to create the buttons and labels etc. The form is created with the exception that the functions passed to on button click events are not being passed correctly. For example I have a function to create a button....
function new_btn ($name, $parent, $x, $y, $l, $h, $text, $onClick){
$object = New-Object System.Windows.Forms.Button
$object.Location = New-Object System.Drawing.Point($x, $y)
$Object.Size = New-Object System.Drawing.Size($l, $h)
$Object.Text = $text
$object.add_Click({$onClick})
New-Variable $name -Value $object -Scope global
(Get-Variable $parent).Value.Controls.Add((Get-Variable $name).value)
}
I then have the function that I want to run on the button click.....
function msg {
[System.Windows.Forms.MessageBox]::Show("We are proceeding with next step.")
}
I then call the function and feed it the parameters.......
new_btn getdbslist tab1 20 50 69 23 "Get DB's" msg
This produces the button as expected and adds it to tab1, but the on click event will not work, nothing happens at all. Any help would be very appreciated!
You are just passing a string. Instead pass a script block:
new_btn getdbslist tab1 20 50 69 23 'Get DBs' {
[System.Windows.Forms.MessageBox]::Show("We are proceeding with next step.")
}
And in your new_btn function you probably just need to use
$object.add_Click($onClick)
If you really want to pass a string, then you probably need to use the following:
$object.add_Click({ & $onClick })