Defining variable expansion for .NET objects in PowerShell - variables

Quick background: I am fairly new to PowerShell but am a well-versed C# dev. I have mixed feelings about PowerShell so far. I love it one day, hate it the next. Just when I think I've got it figured out, I get stumped for hours trial-and-error-ing some feature I think it should implement but doesn't.
I would like PowerShell to let me override an object's ToString() method (which it does) such that when an object is referenced inside a double-quoted string, it will call my custom ToString method (which it does not).
Example:
PS C:\> [System.Text.Encoding] | Update-TypeData -Force -MemberType ScriptMethod -MemberName ToString -Value { $this.WebName }
PS C:\> $enc = [System.Text.Encoding]::ASCII
PS C:\> $enc.ToString() ### This works as expected
us-ascii
PS C:\> "$enc" ### This calls the object's original ToString() method.
System.Text.ASCIIEncoding
How does one define the variable expansion behavior of a .NET object?

The language specification doesn't say anything about overriding variable substitution, so I don't think there's a defined way of doing exactly what you want. If a single class was involved you could subclass it in C#, but for a class hierarchy the nearest I can think of is to produce a wrapper around the object which does have your desired behaviour.
$source = #"
using System.Text;
public class MyEncoding
{
public System.Text.Encoding encoding;
public MyEncoding()
{
this.encoding = System.Text.Encoding.ASCII;
}
public MyEncoding(System.Text.Encoding enc)
{
this.encoding = enc;
}
public override string ToString() { return this.encoding.WebName; }
}
"#
Add-Type -TypeDefinition $Source
Then you can use it like this:
PS C:\scripts> $enc = [MyEncoding][System.Text.Encoding]::ASCII;
PS C:\scripts> "$enc"
us-ascii
PS C:\scripts> $enc = [MyEncoding][System.Text.Encoding]::UTF8;
PS C:\scripts> "$enc"
utf-8
PS C:\scripts> $enc.encoding
BodyName : utf-8
EncodingName : Unicode (UTF-8)
HeaderName : utf-8
WebName : utf-8
WindowsCodePage : 1200
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.EncoderReplacementFallback
DecoderFallback : System.Text.DecoderReplacementFallback
IsReadOnly : True
CodePage : 65001
If you don't like the extra .encoding to get at the underlying object you could just add the desired properties to the wrapper.

I stumbled upon a semi-solution, and discovered yet another strange behavior in Powershell. It appears that when string expansion involves a collection, the user-defined ScriptMethod definition of the ToString() method is called, as opposed to the object's original ToString() method.
So, using the above example, all that is necessary is adding one lousy comma:
PS C:\> [System.Text.Encoding] | Update-TypeData -Force -MemberType ScriptMethod -MemberName ToString -Value { $this.WebName }
PS C:\> $enc = ,[System.Text.Encoding]::ASCII # The unary comma operator creates an array
PS C:\> "$enc" ### This now works
us-ascii
This seems like a bug to me.

Related

HOCON config file dynamic substitution

I'm using HOCON to configure log messages and I'm looking for a way to substitute placeholder values dynamically.
I know that ${?PLACEHOLDER} will read an environment variable and returns an empty string when the PLACEHOLDER environment variable doesn't exist.
Example
This is an example of what I had in mind:
(I'm using config4k to load HOCON )
data class LogMessage(val message: String, val code: String)
fun getMessage(key: String, placeholderValues: Array<String> = arrayOf()): LogMessage {
val config = ConfigFactory.parseString("MY_LOG_MESSAGE {code = ABC-123456, message = My log message with dynamic value %0% and another dynamic value %1% }")
val messageObject = config.extract<LogMessage>(key)
var message = messageObject.message
placeholderValues.forEachIndexed { placeholderIndex, value ->
message = message.replace("%$placeholderIndex%", value)
}
return messageObject.copy(message = message)
}
fun main(args: Array<String>) {
println(getMessage("MY_LOG_MESSAGE", arrayOf("value 1", "value 2")))
// prints: LogMessage(message=My log message with dynamic value value 1 and another dynamic value value 2, code=ABC-123456)
}
Even though this works, it doesn't look like the best solution and I assume there already is a solution for this.
Could someone tell me if there is a built-in solution?
First things first.
HOCON is just a glorified JSON format.
config4k is just a wrapper.
All your work is being done by Typesafe Config, as you've probably noticed.
And judging by their documentation and code, they support placeholders only from withing the file, or from the environment:
This library limits itself to config files. If you want to load config
from a database or something, you would need to write some custom
code.
But for what you're doing, simple String.format() should be enough:
fun interpolate(message: String, vararg args: Any) = String.format(message, *args)
println(interpolate("My message was %s %s %s %s", "a", 1, 3.32, true))
// My message was a 1 3.32 true
Notice that you can use * to destructure your array.

PHP 5.6.* vs 7.0.* syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)

Strange my research has not turned'up this exact scenario:
$someVar = $this->StaticClassName::$staticClassProperty;
php 7.* happily accesses the property but 5.6.* (.11 in this case) falls over with the error:
unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
And yes, I've tried every ${permutation}:: I could think of.
There's a lot of compound expressions like this which don't work in PHP 5. Usually the solution is to break it into multiple expressions, and you can do that here:
$className = $this->StaticClassName;
$someVar = $className::$staticClassProperty;
This works on both PHP 5 and PHP 7.
Well, here at least is one solution that may be passable for your needs:
<?php
class MyClass {
public $childClass;
public function __construct() {
$this->childClass = new ChildClass();
}
}
class ChildClass {
public static $foo = 'bar';
public function getFoo() {
return static::$foo;
}
}
$obj = new MyClass();
echo $obj->childClass->getFoo();
?>

How to add subscripts to my custom Class in Perl 6?

I am new to Perl 6. I have the following code in my Atom Editor, but I still don't understand how this works. I copied the following code, as the docs.raku.org said, but it seems that it does not work. So I changed the code to this:
use v6;
class HTTPHeader { ... }
class HTTPHeader does Associative {
has %!fields handles <self.AT-KEY self.EXISTS-KEY self.DELETE-KEY self.push
list kv keys values>;
method Str { say self.hash.fmt; }
multi method EXISTS-KEY ($key) { %!fields{normalize-key $key}:exists }
multi method DELETE-KEY ($key) { %!fields{normalize-key $key}:delete }
multi method push (*#_) { %!fields.push: #_ }
sub normalize-key ($key) { $key.subst(/\w+/, *.tc, :g) }
method AT-KEY (::?CLASS:D: $key) is rw {
my $element := %!fields{normalize-key $key};
Proxy.new(
FETCH => method () { $element },
STORE => method ($value) {
$element = do given $value».split(/',' \s+/).flat {
when 1 { .[0] } # a single value is stored as a string
default { .Array } # multiple values are stored as an array
}
}
);
}
}
my $header = HTTPHeader.new;
say $header.WHAT; #-> (HTTPHeader)
"".say;
$header<Accept> = "text/plain";
$header{'Accept-' X~ <Charset Encoding Language>} = <utf-8 gzip en>;
$header.push('Accept-Language' => "fr"); # like .push on a Hash
say $header.hash.fmt;
"".say;
say $header<Accept-Language>.values;
say $header<Accept-Charset>;
the output is:
(HTTPHeader)
Accept text/plain
Accept-Charset utf-8
Accept-Encoding gzip
Accept-Language en fr
(en fr)
utf-8
I konw it works, but the document in docs.raku.org is a little different to this, which doesn't have "self" before the AT-KEY method in the 7th line. Is there any examples that more detail about this?
Is there any examples that more detail about this?
Stack overflow is not really the place to request more detail on a published example. This is the perl6 doco on the community itself - I would suggest that the most appropriate place if you have further queries is the Perl6 users mailing list or, failing that, the IRC channel, perhaps.
Now that you've posted it though, I'm hesitant to let the question go unaddressed so, here are a couple of things to consider;
Firstly - the example you raised is about implementing associative subscripting on a custom (ie user defined) class - it's not typical territory for a self-described newbie. I think you would be better off looking at and implementing the examples at Perl6 intro by Naoum Hankache whose site has been very well received.
Option 1 - Easy implementation via delegation
Secondly, it's critical to understand that the example is showing three options for implementing associative subscripting; the first and simplest uses delegation to a private hash attribute. Perl6 implements associative and positional subscripts (for built-in types) by calling well-defined methods on the object implementing the collection type. By adding the handles trait on the end of the definition of the %!fields attribute, you're simply passing on these method calls to %!fields which - being a hash - will know how to handle them.
Option 2 - Flexible keys
To quote the example:
However, HTTP header field names are supposed to be case-insensitive (and preferred in camel-case). We can accommodate this by taking the *-KEY and push methods out of the handles list, and implementing them separately...
Delegating all key-handling methods to the internal hash means you get hash-like interpretation of your keys - meaning they will be case-sensitive as hash keys are case-sensitive. To avoid that, you take all key-related methods out of the handles clause and implement them yourself. In the example, keys are ran through the "normalizer" before being used as indexes into %!fields making them case-insensitive.
Option 3 - Flexible values
The final part of the example shows how you can control the interpretation of values as they go into the hash-like container. Up to this point, values supplied by assigning to an instance of this custom container had to either be a string or an array of strings. The extra control is achieved by removing the AT_KEY method defined in option 2 and replacing it with a method that supplies a Proxy Object. The proxy object's STORE method will be called if you're assigning to the container and that method scans the supplied string value(s) for ", " (note: the space is compolsory) and if found, will accept the string value as a specification of several string values. At least, that's what I think it does.
So, the example has a lot more packed into it than it looks. You ran into trouble - as Brad pointed out in the comments - because you sort-of mashed option 1 togeather with option 3 when you coppied the example.

Powershell WCF service DateTime property always DateTime.Min (01.01.0001)

i've detected a strange behavior calling a WCF Service from a Powershell script. Using the command 'New-WebServiceProxy' from Powershell 2.0 get's you the abillity to send requests to a Webservice from a PS script. But i got some problems with System.DateTime objects on the service side, the value on the server side is always DateTime.Min.
So i created a small test service an script and i can reproduce this error. I used a 'standard' WCF-Project from VS2010 and extedended the 'DataContract' Class with a DateTime Property:
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
[DataMember]
public DateTime Datum { get; set; }
}
Powershell script to call the service:
cls
$serv = New-WebServiceProxy -uri 'http://localhost:50176/TestService.svc?wsdl' - Namespace wt
$data = [wt.CompositeType](New-Object wt.CompositeType)
$data.StringValue = "abcd"
$data.BoolValue = $true
$data.Datum = Get-Date
$serv.GetDataUsingDataContract($data)
If needed, i can send you a dropbox link for the zipped project.
Regards Uwe
I've never used powershell before but thought I'd take a long overdue look at it for this question!
The proxy object $data can have a date property set but, despite what your code looks like its doing, $data isn't the real object, just an XML proxy of it.
If you enter command "$data" you'll see what looks like an XmlSerialized version of the object (has xxSpecified properties for the bool and DateTime). It does reflect changes made by e.g. "$data.Datum = Get-Date".
The proxy is deserialised back to an instance of MyCompositeType when you call GetUsingDataContract (as its passed as a parameter and sent using XML) which you can see by putting breakpoints on the property get/setters prior to calling it.
As part of this deserialization, only the StringValue makes it which is because the Xml serialization for the other properties will only include values where "xxxSpecified" is true.
If you set the "xxxSpecified" properties in the proxy they will serialize back correctly.
But the best fix is to change their DataMember attribute to:
[DataMember(IsRequired=true)]
Which should just work with the code you've got.

PowerShell Remoting Serialization and Deserialization

Are the routines for serializing and deserializing objects from PowerShell (as performed by PowerShell Remoting) available?
I'd like to avoid having to write the objects to disk (with Export-CliXML) and reading it back in with (Import-CliXML).
Basically, I want to get the property bags that the deserialization creates so that I can add them to an AppFabric object cache. Otherwise, AppFabric tries to use .NET serialization, which fails for a number of the standard object types.
Perhaps through the $host or $executioncontext variables?
They have published the PowerShell Remoting Specification which would give you the spec, but the source code they used to implement it is not public at this time. http://msdn.microsoft.com/en-us/library/dd357801(PROT.10).aspx
Oh, I see what you're asking, you're looking for a ConvertTo-CliXml similar to how ConvertTo-Csv works in place of Export-Csv. At first glance it sounds like you're trying to avoid CliXml entirely.
In that case, there's one on PoshCode: ConvertTo-CliXml ConvertFrom-CliXml
Here's a verbatim copy to give you an idea (I haven't checked this for correctness):
function ConvertTo-CliXml {
param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
[PSObject[]]$InputObject
)
begin {
$type = [PSObject].Assembly.GetType('System.Management.Automation.Serializer')
$ctor = $type.GetConstructor('instance,nonpublic', $null, #([System.Xml.XmlWriter]), $null)
$sw = New-Object System.IO.StringWriter
$xw = New-Object System.Xml.XmlTextWriter $sw
$serializer = $ctor.Invoke($xw)
$method = $type.GetMethod('Serialize', 'nonpublic,instance', $null, [type[]]#([object]), $null)
$done = $type.GetMethod('Done', [System.Reflection.BindingFlags]'nonpublic,instance')
}
process {
try {
[void]$method.Invoke($serializer, $InputObject)
} catch {
Write-Warning "Could not serialize $($InputObject.GetType()): $_"
}
}
end {
[void]$done.Invoke($serializer, #())
$sw.ToString()
$xw.Close()
$sw.Dispose()
}
}