i want to add items to arraylist object inside the class. how to do it. below is powershell code. it doesnt allow. how to achive this with
1. array declared at global level
2. array declared at class level
$logArrayGlobal = [System.Collections.ArrayList]::new()
class LogManager {
$logArrayClass = [System.Collections.ArrayList]::new()
LogManager()
{
$logArrayGlobal.Add("sada")
$this.logArrayClass
}
}
PowerShell will allow you to write to non-local variables from within a class method body if you explicitly specify the variables scope:
$logArrayGlobal = [System.Collections.ArrayList]::new()
class LogManager {
$logArrayClass = [System.Collections.ArrayList]::new()
LogManager()
{
$global:logArrayGlobal.Add("sada")
}
}
Related
I'm writing a function that's basically a convenience wrapper around an external program that it'll invoke. Several of the parameters will be forwarded to the external program, but not all.
I was writing
$ArgsToFwd = #()
switch ($PSBoundParameters.Keys) {
'--server' {$ArgsToFwd += #('--server',$server)}
'--userid' {$ArgsToFwd += #('--userid',$userid)}
...
}
but then I thought it might be better to define a custom parameter attribute that could let me do something like:
params(
[Parameter()]
[IsExternal()]
[string]$Server
)
#...
foreach ($key in $PSBoundParameters.Keys) {
if (<#a test for the custom [IsExternal] attribute#>) {
$ArgsToFwd += #($key, $PSBoundParameters[$key])
}
}
But I can't quite figure it out. Can that be done?
If you want to use custom attributes, there are 3 things you need to do:
Define a custom Attribute type
Decorate your function's parameters with instances of said attribute
Write a mechanism to discover the attribute decorations and do something with them at runtime
Let's start with step 1, by defining a custom class that inherits from System.Attribute:
class ProxyParameterAttribute : Attribute
{
[string]$Target
ProxyParameterAttribute([string]$Target){
$this.Target = $Target
}
}
Attribute annotations map directly back to the target attribute's constructor, so in this case, we'll be using it like [ProxyParameter('someValue')] and 'someValue' will then be stored in the $Target property.
Now we can move on to step 2, decorating our parameters with the new attribute. You can omit the Attribute part of the name when applying it in the param block, PowerShell is expecting all annotations to be attribute-related anyway:
function Invoke-SomeProgram
{
param(
[ProxyParameter('--server')]
[Parameter()]
[string]$Server
)
# ... code to resolve ProxyParameter goes here ...
}
For step 3, we need a piece of code that can discover the attribute annotations on the parameters of the current command, and use them to map the input parameter arguments to the appropriate target names.
To discover declared parameter metadata for the current command, the best entry point is $MyInvocation:
# Create an array to hold all the parameters you want to forward
$argumentsToFwd = #()
# Create mapping table for parameter names
$paramNameMapping = #{}
# Discover current command
$currentCommandInfo = $MyInvocation.MyCommand
# loop through all parameters, populate mapping table with target param names
foreach($param in $currentCommandInfo.Parameters.GetEnumerator()){
# attempt to discover any [ProxyParameter] attribute decorations
$proxyParamAttribute = $param.Value.Attributes.Where({$_.TypeId -eq [ProxyParamAttribute]}, 'First') |Select -First 1
if($proxyParamAttribute){
$paramNameMapping[$param.Name] = $proxyParamAttribute.Target
}
}
# now loop over all parameter arguments that were actually passed by the caller, populate argument array while taking ProxyParameter mapping into account
foreach($boundParam in $PSBoundParameters.GetEnumerator()){
$name = $boundParam.Name
$value = $boundParam.Value
if($paramNameMapping.ContainsKey[$name]){
$argumentsToFwd += $paramNameMapping[$name],$value
}
}
Now that the parameters how been filtered and renamed appropriately, you can invoke the target application with the correct arguments via splatting:
.\externalApp.exe #argumentsToFwd
Putting it all together, you end up with something like:
class ProxyParameterAttribute : Attribute
{
[string]$Target
ProxyParameterAttribute([string]$Target){
$this.Target = $Target
}
}
function Invoke-SomeProgram
{
param(
[ProxyParameter('--server')]
[Parameter()]
[string]$Server
)
# Create an array to hold all the parameters you want to forward
$argumentsToFwd = #()
# Create mapping table for parameter names
$paramNameMapping = #{}
# Discover current command
$currentCommandInfo = $MyInvocation.MyCommand
# loop through all parameters, populate mapping table with target param names
foreach($param in $currentCommandInfo.Parameters.GetEnumerator()){
# attempt to discover any [ProxyParameter] attribute decorations
$proxyParamAttribute = $param.Value.Attributes.Where({$_.TypeId -eq [ProxyParamAttribute]}, 'First') |Select -First 1
if($proxyParamAttribute){
$paramNameMapping[$param.Name] = $proxyParamAttribute.Target
}
}
# now loop over all parameter arguments that were actually passed by the caller, populate argument array while taking ProxyParameter mapping into account
foreach($boundParam in $PSBoundParameters.GetEnumerator()){
$name = $boundParam.Name
$value = $boundParam.Value
if($paramNameMapping.ContainsKey[$name]){
$argumentsToFwd += $paramNameMapping[$name],$value
}
}
externalApp.exe #argumentsToFwd
}
You can add other properties and constructor arguments to the attributes to store more/different data (a flag to indicate whether something is just a switch, or a scriptblock that transforms the input value for example).
If you need this for multiple different commands, extract the step 3 logic (discovering attributes and resolving parameter names) to a separate function or encapsulate it in a static method on the attribute class.
I can populate a Generic.List with instances of my class, each with a unique value in the ID property, like this.
$packages = [System.Collections.Generic.List[object]]::New()
class Package {
# Properties
[String]$ID
# Constructor
Package ([String]$id) {
$this.ID = $id
}
}
foreach ($i in 65..90) {
$packages.Add([Package]::New("$([char]$i)"))
}
Now I want to get the index of a particular item, along the lines of $packages.IndexOf('C'). But doing this in C# seems to require the use of a lambda, and PowerShell doesn't seem to support that. I would rather not have to initialize my own index variable and iterate through all the items in the list checking their IDs and incrementing the index along the way. But, maybe this is that grey area between using PowerShell with only native PS cmdlets and just using C#, where you miss out on the sugar in both extremes and have to make your own?
You can use a [scriptblock] as a predicate:
$packages.FindIndex({$args[0].ID -eq 'C'})
# or
$packages.FindIndex({param($item) $item.ID -eq 'C'})
Assuming your package IDs are unique, consider using a dictionary instead of a list:
$packages = [System.Collections.Generic.Dictionary[string,Package]]::new([StringComparer]::InvariantCultureIgnoreCase)
class Package {
# Properties
[String]$ID
# Constructor
Package ([String]$id) {
$this.ID = $id
}
}
foreach ($i in 65..90) {
$ID = "$([char]$i)"
$packages.Add($ID, [Package]::new($ID))
}
Now you don't need to worry about the position of the item, anymore, now you can simply remove one by ID:
$wasRemoved = $packages.Remove("C")
Remove() returns $true when it successfully removes an entry, $false when the key wasn't found
I want to use a func delegate in my static PowerShell 5.0 class:
I had issues to find a way to assign other static class methods for my delegate.
This code is working but not very convinient.
Is there a better way to use a delegate here ?
And I need to instanciate my static! class, only to get the type.
I tried the outcommented line, how you would do it with .NET types, but it's not working for my own class.
How can I get the type of my static class here more elegant ?
And, BTW, GetMethod() did not accecpt the BindingFlags parameter, why ?
class Demo
{
hidden static [object] Method_1([string] $myString)
{
Write-Host "Method_1: $myString"
return "something"
}
hidden static [object] Method_2([string] $myString)
{
Write-Host "Method_2: $myString"
return $null
}
hidden static [object] TheWrapper([string]$wrappedMethod, [string] $parameter)
{
# do a lot of other stuff here...
#return [System.Type]::GetType("Demo").GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
return [Demo]::new().GetType().GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
}
static DoWork()
{
Write-Host ([Demo]::TheWrapper('Method_1', 'MyMessage'))
[Demo]::TheWrapper('Method_2', 'c:\my_file.txt')
}
}
[Demo]::DoWork()
You don't need to create an instance of [demo] since [demo] is the actual type of the class. Also, you can write the delegate type more simply as [Func[string,object]]. This simplifies the body of TheWrapper method to
return [Demo].GetMethod($wrappedMethod).CreateDelegate([Func[string, object]]).Invoke($parameter)
but a much simpler way to do this in PowerShell is to get the method by passing its name to the '.' operator then invoking the result:
return [demo]::$wrappedMethod.Invoke($parameter)
In PowerShell, the right-hand side of the '.' operator doesn't need to be a constant. You can use an expression that results in the name of the method (or property) to retrieve.
Is it possible to have the value of a property of a PowerShell class defined on instantiation without using a constructor?
Let's say there's a cmdlet that will return Jon Snow's current status (alive or dead). I want that cmdlet to assign that status to a property in my class.
I can do this using a constructor, but I'd like this to happen regardless of which constructor is used, or even indeed if one is used at all.
function Get-JonsCurrentStatus {
return "Alive"
}
Class JonSnow {
[string]
$Knowledge
[string]
$Status
#Constructor 1
JonSnow()
{
$this.Knowledge = "Nothing"
$this.Status = Get-JonsCurrentStatus
}
#Constructor 2
JonSnow([int]$Season)
{
if ($Season -ge 6)
{
$this.Knowledge = "Still nothing"
$this.Status = Get-JonsCurrentStatus #I don't want to have to put this in every constructor
}
}
}
$js = [JonSnow]::new()
$js
Unfortunately, you cannot call other constructors in the same class with : this() (though you can call a base class constructor with : base())[1]
Your best bet is a workaround with a (hidden) helper method:
function Get-JonsCurrentStatus {
return "Alive"
}
Class JonSnow {
[string]
$Knowledge
[string]
$Status
# Hidden method that each constructor must call
# for initialization.
hidden Init() {
$this.Status = Get-JonsCurrentStatus
}
#Constructor 1
JonSnow()
{
# Call shared initialization method.
$this.Init()
$this.Knowledge = "Nothing"
}
#Constructor 2
JonSnow([int]$Season)
{
# Call shared initialization method.
$this.Init()
if ($Season -ge 6)
{
$this.Knowledge = "Still nothing"
}
}
}
$js = [JonSnow]::new()
$js
[1] The reason for this by-design limitation, as provided by a member of the PowerShell team is:
We did not add : this() syntax because there is a reasonable alternative that is also somewhat more intuitive syntax wise
The linked comment then recommends the approach used in this answer.
You can initialise class properties on instantiation this way:
$jon = new-object JonSnow -Property #{"Status" = Get-JonsCurrentStatus; "Knowledge" = "Nothing"}
Is it possible to have a property in a class reference another? For example, something like this. I've tried a few ways and I'm now not sure if I can do this...:
class TestClass {
[string]
$SQLInstanceName
[string]
$Server = "$($env:COMPUTERNAME)\$SQLInstanceName"
[string]myResult()
{
return $this.Server
}
}
....Thanks
Yes. Here it is implemented in your class definition:
class TestClass {
[string]
$SQLInstanceName
hidden $_Server = $($this | Add-Member ScriptProperty 'Server' `
{
# get
"$($env:COMPUTERNAME)\$($this.SQLInstanceName)"
}
)
[string]myResult()
{
return $this.Server
}
}
To see this working, new up an instance and assign a value to SQLInstanceName.
$c = [TestClass]::new()
$c.SQLInstanceName = 'MyDB'
Then invoking
$c.Server
$c.myResult()
results in
ComputerName\MyDB
ComputerName\MyDB
You should be using $this if you want to refer to a non-static property/method in the same object just like you have done with your myResult() method. Also your current sample has no default value or constructor so the SQLInstanceName is blank so just adding $this, without setting the variable, might give you misleading results. The following example might be something to consider but it is flawed.
class TestClass {
[string]$SQLInstanceName = "Test"
[string]$Server = "$($env:COMPUTERNAME)\$($this.SQLInstanceName)"
[string]myResult()
{
return $this.Server
}
}
$tcobject = New-Object TestClass
$tcobject.myResult()
However this does not work if you change the SQLInstanceName property since you are just setting default values. Classes in v5 don't really have get and set truly implemented in the same way as a .Net class so you would have to roll your own solution for that as discussed in this answer but also on this blog about v5 classes in general.
So a simple solution would work like this to get what I think you want.
class TestClass {
[string]
$SQLInstanceName = "Test"
[string]
$Server = $env:COMPUTERNAME
[string]myResult()
{
return "$($this.Server)\$($this.SQLInstanceName)"
}
}
$tcobject = New-Object TestClass
$tcobject.SQLInstanceName = "server\prod"
$tcobject.myResult()
This would be a design choice but I would not be trying to dynamically change one property based on the value of another in this case. Since you are using a value of them combined a simple method could work.