Accessing fixed size array elements from C# in PowerShell - powershell

Looking for a way to access individual array elements (continuation of this question)
$CSharpCode = #"
using System;
namespace TestStructure
{
public struct TestStructure
{
public byte Field1;
public unsafe fixed byte Field2[4];
}
}
"#
$cp = New-Object System.CodeDom.Compiler.CompilerParameters
$cp.CompilerOptions = '/unsafe'
Add-Type -TypeDefinition $CSharpCode -CompilerParameters $cp
function ConvertTo-Struct
{
# Only accept struct types (sealed value types that are neither primitive nor enum)
param(
[Parameter(Mandatory = $true)]
[ValidateScript({ $_.IsValueType -and $_.IsSealed -and -not($_.IsPrimitive -or $_.IsEnum) })]
[Type]$TargetType,
[Parameter(Mandatory = $true)]
[byte[]]$BinaryData
)
# Start by calculating minimum size of the underlying memory allocation for the new struct
$memSize = [System.Runtime.InteropServices.Marshal]::SizeOf([type]$TargetType)
# Make sure user actually passed enough data to initialize struct
if($memSize -gt $BinaryData.Length){
Write-Error "Not enough binary data to create an instance of [$($TargetType.FullName)]"
return
}
# now we just need some unmanaged memory in which to create our struct instance
$memPtr = [IntPtr]::Zero
try {
$memPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($memSize)
# copy byte array to allocated unmanaged memory from previous step
[System.Runtime.InteropServices.Marshal]::Copy($BinaryData, 0, $memPtr, $memSize)
# then marshal the new memory region as a struct and return
return [System.Runtime.InteropServices.Marshal]::PtrToStructure($memPtr, [type]$TargetType)
}
finally {
# and finally remember to clean up the allocated memory
if($memPtr -ne [IntPtr]::Zero){
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($memPtr)
}
}
}
$testStructure = ConvertTo-Struct -TargetType ([TestStructure.TestStructure]) -BinaryData (1..100 -as [byte[]])
$testStructure.Field1
$testStructure.Field2
this produces the following output:
1
FixedElementField
-----------------
2
only the first array element of $Field2 is visible, can't access others using $testStructure.Field2[x]
Looking for a way to iterate over FixedBuffer of known type / size
$testStructure.Field2.GetType() says <Field2>e__FixedBuffer0
$testStructure.Field2.FixedElementField.GetType() is byte
can't see a way to access other elements of the array.

PowerShell's type adapter doesn't really have anything in place to handle unsafe fixed byte[] fields, effectively raw pointers from the underlying type system's point of view.
The declared size of the underlying memory allocation is only stored in metadata, which you can locate as follows:
# Locate appropriate field metadata
$fieldInfo = $testStructure.GetType().GetField('Field2')
# Discover fixed buffer attribute
$fixedBufferAttribute = $fieldInfo.CustomAttributes.Where({$_.AttributeType -eq [System.Runtime.CompilerServices.FixedBufferAttribute]}) |Select -First 1
Now we can figure out the size and which array element type is expected:
if($fixedBufferAttribute)
{
# Grab array element type + size from FixedBuffer attribute
$elemType,$size = $fixedBufferAttribute.ConstructorArguments
# Create array of appropriate size
$values = $elemType.MakeArrayType()::new($size)
# Copy values from fixed buffer pointer to managed array
try {
$fixedBufferHandle = [System.Runtime.InteropServices.GCHandle]::Alloc($TestStructure.Field2, 'Pinned')
[System.Runtime.InteropServices.Marshal]::Copy($fixedBufferHandle.AddrOfPinnedObject(), $values, 0, $size)
return $values
}
finally {
$fixedBufferHandle.Free()
}
}
You'll find $values now contains the expected byte values 2, 3, 4 and 5

Related

converting javascript "new RegExp" into powershell [regex]::new()

how would i implement the following javascript code snippet in powershell?
String.prototype.regexCount = function (pattern) {
if (pattern.flags.indexOf("g") < 0) {
pattern = new RegExp(pattern.source, pattern.flags + "g");
}
return (this.match(pattern) || []).length;
};
I'm thinking its something like this:
$regexCount = {
param(
$pattern
)
# ??????
if ($pattern.flags.indexOf("g") -lt 0) {
# ????
# $pattern = new RegExp(pattern.source, pattern.flags + "g");
$pattern = [regex]::new($pattern)
}
# ????
return ($this.match($pattern) || []).length;
}
I have almost the entire script converted into powershell except for this little nugget of code... Actually, i'm a little bit clueless when javascript starts creating lambda functions with regular expression objects...
for instance what's the significants of string.prototype.somename? wouldn't you just save the lambda to any variable name?
Using Update-TypeData, create a type-level ScriptMethod ETS member for the .NET string type (System.String):
Update-TypeData -TypeName System.String -MemberName RegexCount -MemberType ScriptMethod -Value {
param([regex] $Regex)
$Regex.Matches($this).Count
}
Now you can call the .RegexCount() method on any string instance, analogous to what your JavaScript code does.
Sample call:
'foo'.RegexCount('.') # -> 3
That is, 3 matches for regex . were found in the input string.

Cannot index into a null array, but there is no array, it's a hash table, and the hash table isn't empty

I have the following code, which is a bit more than minimally functional, but I hope it's still understandable.
$data = 'PATH TO SOME FILE OR FOLDER'
$rule = #{
property = 'size'
operator = 'lt'
value = '1000gb'
note = $null
logOnForgo = $false
}
$ruleOperationArguments = #{
operator = $rule.operator
operand1 = $Null # always the current state to be tested, and depends on specific rule
logString1 = $rule.property
operand2 = $rule.value
logString2 = $rule.value # sometimes modified by rule
note = $rule.note
logOnForgo = $rule.logOnForgo
}
if (($item = Get-Item $data).PSIsContainer) {
$actualSize = 0
foreach ($childItem in (Get-ChildItem $item -recurse | Where {-not $_.PSIsContainer} | ForEach-Object {$_.FullName})) {
$actualSize += (Get-Item $childItem).length
}
} else {
$actualSize = $item.length
}
$ruleOperationArguments.operand1 = $actualSize
if ($actualSize -gt 1TB) {
$actualSizeString = "$([math]::Round(($actualSize/1TB),2))TB"
} elseif ($actualSize -gt 1GB) {
$actualSizeString = "$([math]::Round(($actualSize/1GB),2))GB"
} elseif ($actualSize -gt 1mb) {
$actualSizeString = "$([math]::Round(($actualSize/1MB),2))MB"
} elseif ($actualSize -gt 1kb) {
$actualSizeString = "$([math]::Round(($actualSize/1KB),2))KB"
} else {
$actualSizeString = "$([math]::Round(($actualSize),2))Bytes"
}
$ruleOperationArguments.logString1 = "$($ruleOperationArguments.logString1) ($actualSizeString)"
# test size
switch ($interval[0].Groups['unit'].Value) {
kb {$ruleOperationArguments.operand2 = [System.Int64]($interval[0].Groups['number'].Value) * 1kb}
mb {$ruleOperationArguments.operand2 = [System.Int64]($interval[0].Groups['number'].Value) * 1mb}
gb {$ruleOperationArguments.operand2 = [System.Int64]($interval[0].Groups['number'].Value) * 1gb}
tb {$ruleOperationArguments.operand2 = [System.Int64]($interval[0].Groups['number'].Value) * 1tb}
}
$ruleOperationArguments
I am getting a very odd error
Cannot index into a null array. At line:39 char:41
that line is
$ruleOperationArguments.logString1 = "$($ruleOperationArguments.logString1) ($actualSizeString)"
But here is the odd thing. the code WORKS. The actual final value of $ruleOperationArguments.logString1 IS updated to include the actual size data.
I have also tried not modifying the hash table value by appending to itself, instead using, same result.
$ruleOperationArguments.logString1 = "$($rule.property) ($actualSizeString)"
It's not actually related to the data being put into the hash table, because
$ruleOperationArguments.logString1 = "WTAF?"
also throws the error. What is different about the logStrings that causes problems, while the operands are working without issue? I even tried running the code in a new instance of the ISE, thinking I could have issues with persisting variables. Nope, same issue. I am utterly stumped. I should not that I got a lot fo hits searching on 'Cannot index into a null array' but they all reference actual situations where an array is empty. But $ruleOperationArguments sent to the console will show that the only part of the hash table that is empty is the note key that I am not even using, and operand1 that get's populated before the error condition. Both logStrings have values, and I can successfully update them, but this false error still gets thrown.
EDIT: To minimize the complexity I tried this
$rule = #{
property = 'size'
operator = 'lt'
value = '1000gb'
note = $null
logOnForgo = $false
}
$ruleOperationArguments = #{
operator = $rule.operator
operand1 = $Null
logString1 = $rule.property
operand2 = $rule.value
logString2 = $rule.value
}
$ruleOperationArguments
$ruleOperationArguments.logString1 = "$($ruleOperationArguments.logString1) changed"
$ruleOperationArguments
And thworks with no errors. So something is the rest fo the code is causing the issue, but I still have no clue what.
I think somewhere, you swizzled the line mentioned in the error. When I run the original sample locally, up to the line mentioned in the error and question, the error doesn't occur, and there is nothing that should cause this above. However, running the whole thing results in the same error... but for the switch statement condition. Ignore the line number mismatch here:
Cannot index into a null array.
At line:1 char:9
+ switch ($interval[0].Groups['unit'].Value) {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
Looking more closely at this, $interval isn't defined anywhere. You need to initialize this as an Array, List, or some other type of collection.

[Class]::New() causing blank line on the console

I am working on implementing a singleton class to store some regularly accessed status information for my script, including hacking around the issue of $myInvocation only being populated in the main script. All working as planned with this.
class pxStatus {
static [pxStatus] $singleton = $null
[string]$Context = 'machine'
[string]$Path = $null
[datetime]$StartTime = (Get-Date)
pxStatus ([string]$path) {
if ([pxStatus]::singleton -eq $null) {
$this.Path = $path
[pxStatus]::singleton = $this
} else {
Throw "Singleton already initialized"
}
}
static [pxStatus] Get() {
if ([pxStatus]::singleton -eq $null) {
Throw "Singleton not yet initialized"
} else {
return [pxStatus]::singleton
}
}
}
CLS
[void]([pxStatus]::New((Split-Path ($myInvocation.myCommand.path) -parent)))
([pxStatus]::Get()).StartTime
([pxStatus]::Get()).Context
([pxStatus]::Get()).Path
With one exception. Even with that [void] on the [pxStatus]::New() line, I am getting a blank line in the console. Even $null = ([pxStatus]::New((Split-Path ($myInvocation.myCommand.path) -parent))) is echoing a blank line to the console. And for the life of me I can't see what is causing it.
It's not new that causes a blank line but ([pxStatus]::Get()).StartTime.
To fix the issue, you may output it as string, i.e. not formatted, e.g. ([pxStatus]::Get()).StartTime.ToString()
You problem has already been diagnosed, but I wanted to take a second to show how to actually implement a singleton-like type in PowerShell (see inline comments):
class pxStatus {
# hide backing field from user
hidden static [pxStatus] $singleton = $null
[string]$Context = 'machine'
[string]$Path = $null
[datetime]$StartTime = (Get-Date)
# hide instance constructor, no one should call this directly
hidden pxStatus ([string]$path) {
# Only allow to run if singleton instance doesn't exist already
if ($null -eq [pxStatus]::singleton) {
$this.Path = $path
} else {
Throw "Singleton already initialized - use [pxStatus]::Get()"
}
}
# Use a static constructor to initialize singleton
# guaranteed to only run once, before [pxStatus]::Get() or [pxStatus]::singleton
static pxStatus () {
# grab the path from context, don't rely on user input
if(-not $PSScriptRoot){
throw "[pxStatus] can only be used in scripts!"
}
# this will only succeed once anyway
[pxStatus]::singleton = [pxStatus]::new($PSScriptRoot)
}
static [pxStatus] Get() {
# No need to (double-)check ::singleton, static ctor will have run already
return [pxStatus]::singleton
}
}
[pxStatus]::Get().StartTime

Class based streamReader & xmlReader locks files, function doesn't

I am refactoring some function based XML reader code to class methods, and seeing some issues. With the function, I can run a test and verify the XML loaded right, then change the XML and test for error conditions. But this class based approach fails due to "the file is open in another program", forcing me to close the console before I can revise the XML.
Initially I was using the path directly in the xmlReader. So I moved to a StreamReader input to the xmlReader. And I even played with creating an all new xmlDocument and importing the root node of the loaded XML into that new xmlDocument. None works.
I suspect the reason the function based version works is because the xmlReader variable is local scope, so it goes out of scope when the function completes. But I'm grasping at straws there. I also read that Garbage Collection could be an issue, so I added [system.gc]::Collect() right after the Dispose and still no change.
class ImportXML {
# Properties
[int]$status = 0
[xml.xmlDocument]$xml = ([xml.xmlDocument]::New())
[collections.arrayList]$message = ([collections.arrayList]::New())
# Methods
[xml.xmlDocument] ImportFile([string]$path) {
$importError = $false
$importFile = ([xml.xmlDocument]::New())
$xmlReaderSettings = [xml.xmlReaderSettings]::New()
$xmlReaderSettings.ignoreComments = $true
$xmlReaderSettings.closeInput = $true
$xmlReaderSettings.prohibitDtd = $false
try {
$streamReader = [io.streamReader]::New($path)
$xmlreader = [xml.xmlreader]::Create($streamReader, $xmlReaderSettings)
[void]$importFile.Load($xmlreader)
$xmlreader.Dispose
$streamReader.Dispose
} catch {
$exceptionName = $_.exception.GetType().name
$exceptionMessage = $_.exception.message
switch ($exceptionName) {
Default {
[void]$this.message.Add("E_$($exceptionName): $exceptionMessage")
$importError = $true
}
}
}
if ($importError) {
$importFile = $null
}
return $importFile
}
}
class SettingsXML : ImportXML {
# Constructor
SettingsXML([string]$path){
if ($this.xml = $this.ImportFile($path)) {
Write-Host "$path!"
} else {
Write-Host "$($this.message)"
}
}
}
$settingsPath = '\\Mac\iCloud Drive\Px Tools\Dev 4.0\Settings.xml'
$settings = [SettingsXML]::New($settingsPath)
EDIT:
I also tried a FileStream rather than a StreamReader, with FileShare of ReadWrite, like so
$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Read
$fileShare = [System.IO.FileShare]::ReadWrite
$fileStream = New-Object -TypeName System.IO.FileStream $path, $fileMode, $fileAccess, $fileShare
Still no luck.
I think you're on the right lines with Dispose, but you're not actually invoking the method - you're just getting a reference to it and then not doing anything with it...
Compare:
PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose
OverloadDefinitions
-------------------
void Dispose()
void IDisposable.Dispose()
PS> _
with
PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose()
PS> _
You need to add some () after the method name so your code becomes:
$xmlreader.Dispose()
$streamReader.Dispose()
And then it should release the file lock ok.

Can't get values when looping over an array of arrays

I created an array like so:
while(#results = $execute->fetchrow())
{
my $active = 'true';
if($results[1] == 0)
{
$active = 'false';
}
my #campaign = ($results[0], $active);
push(#campaign_names, #campaign);
}
Later, when I need to access the name of the campaign (which is the first element of the campaign array), I can't seem to extract it. What is the proper syntax?
foreach $campaign (#campaign_names)
{
print ????;
}
Thanks!
The problem is you're pushing an array onto the end of #campaign_names, when what you want is an array reference. Here's how I'd write it:
while(#results = $execute->fetchrow())
{
my $active = $results[1] ? 'true' : 'false';
push #campaign_names, [ $results[0], $active ];
}
# later
foreach my $campaign( #campaign_names )
{
my $name = $campaign->[0];
my $active = $campaign->[1];
}
I've cleaned it up a bit by using a ternary conditional (?:) to figure out the value of $active. The [ ... ] constructs an anonymous array reference (a scalar pointing to an array) which is then pushed onto #campaign_names.
When we loop over those later, two important things to notice are that we use my in the loop variable to keep it local to the loop block, and that we use -> to dereference the elements in the array pointed to by the array reference.
That's not creating an array of arrays. my #campaign = ($results[0], $active); push(#campaign_names, #campaign); flattens and pushes $results[0] and $active into the #campaign_names array. Instead, push an arrayref:
my #campaign = ($results[0], $active);
push(#campaign_names, \#campaign);
or
my $campaign = [$results[0], $active];
push(#campaign_names, $campaign);
Arrays can only hold scalar values.
You'll want to refer to perldsc as you learn (perldoc perldsc, http://perldoc.perl.org/perldsc.html)