Powershell parsing of a C# file to get values of constants - powershell

I need to parse some C# files to get the values of some constant variables. I know I can do something like
$input = Get-Content C:\somefile.cs ...
then loop over each line and do some text matching.
...but was wondering whether I can utilize some sort of a C# DOM object to get the values of constants?

You can load the type dynamically from the powershell command line, and then evaluate the constant.
Example:
C:\somefile.cs contents:
public static class Foo
{
public const string SOME_CONSTANT = "StackOverflow";
}
Powershell command line:
Add-Type -Path C:\somefile.cs
$constantValue = [Foo]::SOME_CONSTANT

Related

Powershell call class on a string?

Trying to call this class to reverse a string in powershell, but can't figure out how to call it.. very simple question but not sure how to do it
class Reverse{
[string]$Stringy
[string]Reversi(){
return $(this.Stringy[-1..-$Stringy.length] -join "")
}
}
Then i am not sure how to call this class. By itself, the return statement does the job, but i need to call the class, please help, no hints just point to the code there is no help online.
My poor attempt to answer this question, as Abraham pointed out in his comment, you need to create an instance of the class:
class ReverseClass {
[string]$Stringy
ReverseClass([string]$String){
$this.Stringy = $String
}
[string]Reverse(){
return -join $this.Stringy[-1..-$this.Length()]
}
[int]Length(){
return $this.Stringy.Length
}
}
$z = [ReverseClass]'Hello World!'
$z.Reverse() # => !dlroW olleH
Again, I insist, a class is an overkill for what you need to do:
function Reverse-String([string]$String){
-join $String[-1..-$String.Length]
}
Reverse-String 'Hello world!' # => !dlrow olleH
If this class was stored on a file like C:\users\user\my documents\script.ps1 and you wanted to load the class or function to your current session you would need to dot source it (simply add a dot before the path). This will load all everything, classes, functions, variables, etc to your current scope.
For example:
If your current directory is not where the script is stored:
. "C:\users\user\my documents\script.ps1"
If you changed directory to my documents in this case:
. .\script.ps1
Are you sure you need a class with an instance method?
If you change the method to be static instead, you can pass whatever string value as a method parameter and get your result without having to create an instance of the class:
class ReverseClass {
static [string] Reverse([string]$string){
return -join $string[-1..-$string.Length]
}
}
Now you can call it using the static member operator :::
PS ~> [ReverseClass]::Reverse("hello")
olleh
Continuing from my comment.
This...
class Reverse{
[string]$Stringy
[string]Reversi(){
return $(this.Stringy[-1..-$Stringy.length] -join "")
}
}
...is not a PowerShell function. This is how you declare a function in PowerShell.
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this
.EXAMPLE
Another example of how to use this
#>
function Verb-Noun
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
[string]$Param1,
[int]$Param2
)
}
# calling the function
Verb-Noun -Param1 '' -Param2 ''
For strings, there is no reason to explicitly cast them as strings. Anything thing in quotes (single or double), is automatically seen as a string.
This is all documented in the PowerShell help files...
Get-Help -Name About_functions
... and MS Docs.
Though you can do classes in PowerShell v5 and higher. As documented in the Powershell help files...
Get-Help -Name About_classes
... and MS Docs.
class Reverse{
[string]$Stringy
[string]Reversi(){
return $this.Stringy[-1..-$this.Stringy.length] -join ""
}
}
PS C:> $reverse=New-Object Reverse -Property #{Stringy="Hello World"}
PS C:> $reverse.Reversi()
dlroW olleH

How to create a case sensitive Hashtable without generating a PSScriptAnalyzer warning?

By default hash tables created by PowerShell (e.g. $hashtable = #{}) are case insensitive.
For my script I want to explicitly create a case sensitive hash table.
This can be done by:
$hashtable = [hashtable]::new()
or:
$hashtable = New-Object hashtable
But I want to have my script also compliant with the default PSScriptAnalyzer rules. For the above case sensitive hash table examples, the UseLiteralInitializerForHashtable rule cases a warning:
Use literal initializer, #{{}}, for creating a hashtable as they are case-insensitive by default
I would expect to be able to work arround this by specifying the StringComparer, like:
[HashTable]::New(0, [StringComparer]::Ordinal)
But this still generates an error (although [HashTable]::New(0, [StringComparer]::OrdinalIgnoreCase) doesn't).
AFAIK, there is not something like: [StringComparer]::OrdinalMatchCase, or?
How to create a case sensitive Hashtable without generating a PSScriptAnalyzer warning?
PSScriptAnalyzer version: 1.18.3
Tested both Windows PowerShell (5.1) and PowerShell Core (6.2.3)
Steps to reproduce the warning:
Invoke-ScriptAnalyzer -ScriptDefinition '[HashTable]::New(0, [StringComparer]::Ordinal)'
Have you tried to encapsulate the hashtable using inline c# as a custom static class?
Add-Type -typedef #"
using System;
using System.Collections;
namespace myCsharp
{
//-----------------------------------------
public class myHashtable
//-----------------------------------------
{
//-------------------------------------
public static Hashtable GetHashtable()
//-------------------------------------
{
Hashtable ht = new Hashtable( 0, StringComparer.Ordinal);
return ht;
}
}
}
"#
$x = [myCsharp.myHashtable]::GetHashtable()

In validateset is it possible to pass the contents of the file?

In my script , I am passing list of components need to taken build
[ValidateSet("InstalComponent","CoreComponent","SDKComponent","ServiceComponent","TimeComponent")]
$Components=#("InstalComponent","CoreComponent"),
From time to time we are adding new component and the list is too long. If there is a way like passing via file "GEt-content Components.txt". It will be of good. Is there any way to set like this?
One option is to create an [enum] from the file contents, then cast the parameter as that type:
#(
"InstalComponent",
"CoreComponent",
"SDKComponent",
"ServiceComponent",
"TimeComponent") |
set-content Components.txt
Add-Type -TypeDefinition #"
public enum MyComponents
{
$(((Get-Content .\Components.txt) -join ', ').split())
}
"#
function test
{
param ( [MyComponents[]]$Components)
$Components
}
Creating and using enums in Powershell
I may not quite understand the question, but you can validate a parameter using ValidateScript like this:
Create a file "Components.txt" with a line for each option on separate lines, with no quotes or commas. Then validate like this:
param(
[ValidateScript({
$Components = Get-Content .\Components.txt
$Components.Contains($_)
})
]
[String]$Component)
...
Short answer: no.
Longer answer: If you must validate a parameter using a configurable set of values you can still do it the old way by putting the validation at the beginning of the function body:
function Foo {
Param(
[Parameter()]$Bar
)
$validSet = Get-Content 'C:\path\to\components.txt'
if ($validSet -notcontains $Bar) {
throw "Invalid argument for parameter -Bar: $Bar"
}
...
}

Joining together strings into Powershell variable names

I'm working with treeviews in Powershell - I have a different node for each of our buildings. In my code I'm grabbing variables, Joining them together, and using that as the variable name - but my code is seeing the variable as a string instead of the name of a node that already exists... so I'm getting
You cannot call a method on a null-valued expression.
How can I do this? It would save me from hard-coding in every floor in every building. Here's what my code looks like:
$bldg = "SG1-1" //for example
function refreshPrinterList ( $bldg )
{
$bldg1 = $bldg.substring(0,3)
$floor = $bldg.substring(4,1)
$refreshNode = -join('$TreeNode_',$bldg1,'_',$floor)
$refreshNode.gettype() //Returns System.String`
if($bldg1 -eq "SG1") {
if($floor -eq "1") {
$count = $refreshNode.Nodes.Count
while($refreshNode.Nodes.Count -gt 0)
{
$refreshNode.Nodes.RemoveAt($count)
$count--
}
The -join operator is for strings, and dutifully gives you one back instead of a TreeNode that you want. If you are passing in a string ($bldg looks like a string from your example), then you can do all the string manipulation you want, but there is no TreeNode object in that function to assign a name to. So, we need to make a TreeNode that your function could use. What about something like this?
$newNodeName = -join('$TreeNode_',$bldg1,'_',$floor)
$refreshNode = New-Object System.Windows.Forms.TreeNode($newNodeName )
// do stuff with $refreshNode as it is a TreeNode object like you expect
This $refreshNode will have no Nodes inside of it since we just fabbed it up. Since it looks like you want to modify an existing TreeNode object, pass in the $refreshNode as an argument then modify its friendly description with the $newNodeName.
I was pointed in the right direction over on the Technet Social forum
My question on Technet
The answer was using 'Get-Variable'
I had the two variables $bldg1 and $floor which I joined into a string:
$newNodeName = -join('TreeNode_',$bldg1,'_',$floor)
and then I passed that using 'Get-Variable' - but I had to put the variable name within parantheses, like so:
$refreshNode = (Get-Variable ($newNodeName)).Value
Now, instead of returning a string type it returns my existing string!

Preventing PowerShell from wrapping value types in PSObjects

I have a .NET API that uses a lot of delegates. My API has a couple methods similar to the following:
public static class MyClass
{
public static void DoSomethingWithString(Func<object> myFunc)
{
string myStringValue = myFunc().ToString();
Console.WriteLine(myStringValue);
}
public static void DoSomethingWithDouble(Func<object> myFunc)
{
object unparsedValue = myFunc();
double parsedValue = Convert.ToDouble(unparsedValue);
Console.WriteLine(parsedValue);
}
}
Now in PowerShell I have the following:
[MyClass]::DoSomethingWithString({ "Hello" }); # No error here
[MyClass]::DoSomethingWithDouble({ 123.4 }); # InvalidCastException - can't convert a PSObject to double
The problem is that my PowerShell scriptblock is returning a PSObject instead of the actual double value. My .NET API doesn't know anything about PowerShell, and I don't want to add a reference to PowerShell DLLs just so I can add special handling for this particular scenario.
Is there a way to get my scriptblocks to return actual value types rather than PSObjects? Or is there a PowerShell-agnostic way for my .NET library to handle PSObjects?
PowerShell will wrap things in PSObject as it sees fit, there's no clean way to avoid that when accepting arbitrary script blocks. With some discipline, you can write script blocks that unwrap the PSObject, for example the following might work fine:
[MyClass]::DoSomethingWithDouble({ (123.4).PSObject.BaseObject })
If possible, a better option is to have your api take a delegate with a more specific return type.
public static void DoSomethingWithDouble(Func<double> myFunc)
In this case, PowerShell will convert the return value to the type expected by the delegate. When the return type is PSObject, PowerShell knows PSObject trivially converts to object so it doesn't unwrap, but if the return type is pretty much anything else, PowerShell is forced to do the conversion by unwrapping the PSObject.
For completeness, another option if you're using PowerShell V3 is to use the C# keyword dynamic, something like:
public static void DoSomethingWithDouble(Func<object> myFunc)
{
dynamic unparsedValue = myFunc();
double parsedValue = (double)(unparsedValue);
Console.WriteLine(parsedValue);
}
When unparsedValue is a PSObject, PowerShell will perform the conversion from to double on the second line even though you don't reference any PowerShell assemblies in your C# code.
Note these last two options may not fit well with your real API, but they are options that are worth understanding.