Вы не можете создавать свойства ParameterizedProperty непосредственно в Powershell, но вы можете косвенно создавать их, позволяя Powershell обернуть объект PSObject вокруг объекта, обладающего свойством accessor. Затем вы делаете этот объект PSObject объектом NoteProperty для объекта, к которому хотите добавить свойство.
В C # мы говорим о this[]
аксессоре. Я написал скрипт Powershell, который создает минимальный объект .NET, имеющий this[]
аксессор. Чтобы сделать это как можно более универсальным, я попытался скопировать то, что делает член ScriptProperty, и добавил два свойства типа ScriptBlock
- одно для блока Get
, а другое для блока Set
. В сущности, когда пользователь устанавливает метод доступа this[]
, он вызывает блок Set
, а когда пользователь извлекает данные из метода доступа this[]
, он вызывает блок Get
.
Следующий модуль я назвал PSObjectWrappers.psm1 :
<#
.SUMMARY
Creates a new ParameterizedPropertyAccessor object.
.DESCRIPTION
Instantiates and returns an object compiled on the fly which provides some plumbing which allows a user to call a new Parameterized
Property, which looks as if it is created on the parent object. In fact, a NoteProperty is created on the parent object which retrieves
an instance of ParameterizedPropertyAccessor, which has a this[] accessor which Powershell wraps in a ParameterizedProperty object.
When the this[] accessor is retrieved, it tries to retrieve a value via a Get script block. When the this[] accessor is updated, this
triggers a Set script block.
.NOTES
No actual variable value state is stored by this object.
The C# code is conditionally compiled to take advantage of new functionality in Powershell 4. Before this version, the first parameter
in the Set and Get script blocks must be "[PSObject] $this". From this version, the $this parameter is automatically created for the user.
#>
Function New-ParameterizedPropertyAccessor
{
Param(
# Contains the object on which the "ParameterizedProperty" will be added.
[Parameter(Mandatory = $true, Position = 0)]
[PSObject] $Parent,
# The name of the parameterized property.
[Parameter(Mandatory = $true, Position = 1)]
[string] $Name,
# Script block which will be called when the property is retrieved.
# First parameter must be $this. Second parameter must be $key.
[Parameter(Mandatory = $true, Position = 2)]
[scriptblock] $Get,
# Script block which will be called when the property is set.
# First parameter must be $this. Second parameter must be $key. Third parameter must be $value.
[Parameter(Mandatory = $true, Position = 3)]
[scriptblock] $Set
);
# Note. You *MUST* ensure the next line starts at position 1 on the line. Likewise, the last line of the code *MUST*
# start at position 1 on the line.
$csharpCode = @'
using System;
using System.Collections.Generic;
using System.Management.Automation;
public class ParameterizedPropertyAccessor
{
private PSObject _parentPsObject;
private ScriptBlock _getBlock;
private ScriptBlock _setBlock;
public ParameterizedPropertyAccessor(PSObject parentPsObject, string propertyName, ScriptBlock getBlock, ScriptBlock setBlock)
{
_parentPsObject = parentPsObject;
PSVariable psVariable = new PSVariable(propertyName, this, ScopedItemOptions.ReadOnly);
PSVariableProperty psVariableProperty = new PSVariableProperty(psVariable);
_parentPsObject.Properties.Add(psVariableProperty);
_getBlock = getBlock;
_setBlock = setBlock;
}
public object this[object key]
{
get
{
#if WITH_CONTEXT
return _getBlock.InvokeWithContext(null, new List<PSVariable> { new PSVariable("this", _parentPsObject) }, new object[] { key });
#else
return _getBlock.Invoke(new object[] { _parentPsObject, key });
#endif
}
set
{
#if WITH_CONTEXT
_setBlock.InvokeWithContext(null, new List<PSVariable> { new PSVariable("this", _parentPsObject) }, new object[] { key, value });
#else
_setBlock.Invoke(new object[] { _parentPsObject, key, value });
#endif
}
}
}
'@;
<#
The version of the ScriptBlock object in Powershell 4 and above allows us to create automatically declared
context variables. In this case, we are providing a $this object, like you would get if we were using a
ScriptMethod or ScriptProperty member script. If we are using this version, then set the WITH_CONTEXT symbol
to conditionally compile a version of the C# code above which takes advantage of this.
#>
If ($PSVersionTable.PSVersion.Major -ge 4)
{
$compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters;
$compilerParameters.CompilerOptions = "/define:WITH_CONTEXT";
$compilerParameters.ReferencedAssemblies.Add( "System.dll" );
$compilerParameters.ReferencedAssemblies.Add( "System.Core.dll" );
$compilerParameters.ReferencedAssemblies.Add( ([PSObject].Assembly.Location) );
}
# Compiles the C# code in-memory and allows us to instantiate it.
Add-Type -TypeDefinition $csharpCode -CompilerParameters $compilerParameters;
# Instantiates the object.
New-Object ParameterizedPropertyAccessor -ArgumentList $Parent,$Name,$Get,$Set;
}
Обратите внимание, что я сделал условную компиляцию в коде C #, чтобы код работал как правильный ScriptBlock в Powershell 4 и выше, поэтому автоматически предоставляется переменная $this
. В противном случае вы должны убедиться, что первый параметр в каждом блоке сценария называется $this
.
Вот мой тестовый скрипт, Test-PPA.ps1 :
<#
.SYNOPSIS
Test script for the ParameterizedPropertyAccessor object.
#>
<#
.SYNOPSIS
Create a new PSCustomObject which will contain a NoteProperty called Item accessed like a ParameterizedProperty.
#>
Function New-TestPPA
{
# Instantiate our test object.
$testPPA = New-Object -TypeName PSCustomObject;
# Create a new instance of our PPA object, added to our test object, providing it Get and Set script blocks.
# Note that currently the scripts are set up for Powershell 4 and above. If you are using a version of Powershell
# previous to this, comment out the current Param() values, and uncomment the alternate Param() values.
$ppa = New-ParameterizedPropertyAccessor -Parent $testPPA -Name Item -Get `
{
Param(
<#
[Parameter(Mandatory = $true, Position = 0)]
[PSObject] $this,
[Parameter(Mandatory = $true, Position = 1)]
[string] $Key
#>
[Parameter(Mandatory = $true, Position = 0)]
[string] $Key
)
$this._ht[$Key];
} -Set {
Param(
<#
[Parameter(Mandatory = $true, Position = 0)]
[PSObject] $this,
[Parameter(Mandatory = $true, Position = 1)]
[string] $Key,
[Parameter(Mandatory = $true, Position = 2)]
[string] $Value
#>
[Parameter(Mandatory = $true, Position = 0)]
[string] $Key,
[Parameter(Mandatory = $true, Position = 1)]
[string] $Value
)
$this._ht[$Key] = $Value;
};
# Add a HashTable <_ht> used as our backing store. Note that this could be any keyed collection type object.
$testPPA | Add-Member -MemberType NoteProperty -Name _ht -Value @{} -PassThru;
}
[string] $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent;
Import-Module $scriptDir\PSObjectWrappers.psm1;
# Create test object.
$testPPA = New-TestPPA;
# Note that "Item" property is actually a NoteProperty of type ParameterizedPropertyAccessor.
Write-Host "Type '`$testPPA | gm' to see Item NoteProperty.";
# Note that it is the ParameterizedPropertyAccessor object retrieved that has a ParameterizedProperty.
# Also note that Powershell has named this property "Item".
Write-Host "Type '`$testPPA.Item | gm' to see Item ParameterizedProperty";
# Step through what happens when we "set" the "parameterized" Item property.
# Note that this is actually retrieving the Item NoteProperty, and then setting its default accessor, which calls
# the 'Set' ScriptBlock.
Write-Host "";
Write-Host "Setting Name value";
Write-Host "... to 'Mark'."
$testPPA.Item["Name"] = "Mark";
# Step through what happens when we "get" the "parameterized" Item property.
# Note that this is actually retrieving the Item NoteProperty, and then retrieving its default accessor, which calls
# the 'Get' ScriptBlock.
Write-Host "";
Write-Host "Retrieving Name value:";
$temp = $testPPA.Item["Name"];
Write-Host $temp;
Обратите внимание, что вам придется менять блоки скриптов, как указано, если вы используете версии, предшествующие Powershell 4.