PSDllCompiler.psm1

using module .\updater.psm1

using namespace System
using namespace System.IO
using namespace System.Text
using namespace System.Management.Automation

#Requires -Version 3.0

<#
.SYNOPSIS
Compiling PowerShell Class Modules into C# DLLs.
 
.DESCRIPTION
This script compiles PowerShell Class Modules into C# Dynamic Link Libraries (DLLs).
The compiled file can be imported into PowerShell using the "using assembly .\Path.dll" directive,
or utilized with other .NET languages such as C# by adding the DLL to references and importing it using "using static ExampleName;".
 
.PARAMETER Path
Specifies the path to the *.ps1 or *.psm1 script containing only a class and related namespaces.
 
.PARAMETER ScriptDefinition
Provides a string containing the class code.
 
.PARAMETER ModuleReferences
Adds a PowerShell package reference to the assembly. For instance, if "Compile-Dll" within your class has been utilized,
the reference must be included by adding "#include PSDllCompiler" at the beginning, or by using -ModuleReferences @("PSDllCompiler").
 
.PARAMETER OutputAssembly
Specifies the path where the resulting *.cs or *.dll file will be saved.
 
.PARAMETER DebugMode
Enables the GetPowershell() method, as well as access to both ClassHandlers and other features.
 
.PARAMETER SkipUpdate
Doesn't check for updates.
 
.EXAMPLE
Compile-Dll .\example.psm1 -o .\example.dll
 
.EXAMPLE
Compile-Dll .\example.psm1 -o .\example.cs
 
.EXAMPLE
$example = @'
class ExampleClass {
    ExampleClass() {}
 
    [string] $Name
 
    [string] SayHello() {
         
        return "Hello " + $this.Name
    }
}
'@
Compile-Dll -ScriptDefinition $example -OutputAssembly .\ExampleClass.dll
#>



function Compile-Dll {

    [CmdletBinding()]

    param (
        
        [Parameter(Position = 0)]
        [string]$Path,

        [Parameter(ValueFromPipeline)]
        [string]$ScriptDefinition,

        [string[]]$ModuleReferences,

        [Alias("o")]
        [Parameter(Position = 1)]
        [string]$OutputAssembly,

        [switch]$DebugMode,

        [switch]$SkipUpdate
    )

    $ErrorActionPreference = [ActionPreference]::Stop

    #Update Check

    if (!$SkipUpdate) {

        Write-Host "`nChecking for Updates...`n"

        if ([Updater]::UpdateAvailable()) {
        
            Write-Host "New update found!, use 'Update-Module PSDllCompiler'
 
Current version: $([Updater]::CurrentVersion)
Newest version: $([Updater]::NewestVersion)
 
Release Notes:
$([Updater]::GetReleaseNotes())
"

            pause
        }
    }

    

    if ($Path) {$Content = Get-Content $Path -Raw}
    elseif ($ScriptDefinition) {$Content = $ScriptDefinition}
    else {throw "Provide a input code using -Path or -ScriptDefinition !"}

    if (!$OutputAssembly) {throw "Provide a output path location using -OutputAssembly !"}
    if (($OutputAssembly.Split(".") | Select-Object -Last 1) -notin @("dll", "cs")) {throw "Invalid file format! Valid formats: *.dll, *.cs"}
    if (Test-Path $OutputAssembly) {throw "File '$OutputAssembly' allready exists!"}

    #adding class
    Invoke-Expression $Content

    #adding include refs
    [string[]]$ModuleReferences += $Content.Split("`n") | Select-String "#include " -SimpleMatch | ForEach-Object {([string]$_).Replace("#include ", "").Split(",")} | ForEach-Object {$_.Trim()}

    #removing comments (& refs)
    $Content = $Content -split "<#"
    [string]$Content_Clean = ""
    for ($i = 0; $i -lt $Content.Count; $i++) {
        if ($i % 2 -eq 0) {$Content_Clean += $Content[$i]}
        else {$Content_Clean += ($Content[$i] -split "#>")[1].Trim()}
    }
    [string[]]$Content_Lines = $Content_Clean.Split("`n") | ForEach-Object {([string]$_).Split("#")[0]}
    [string]$Content = $Content_Lines -join "`n"

    if ($Content -match "Write-Output") {throw "at $(($Content_Lines | Select-String "Write-Output" -SimpleMatch).LineNumber): Class cannot contain Write-Output !"}

    [string[]]$ClassNames = $Content_Lines | Select-String "class" | Where-Object {!([string]$_).Contains("(") -and !([string]$_).Contains("$")} | ForEach-Object {([string]$_).Split(" ")[1]}

    [string]$Class_Csharp = ""

    for ([int]$progress = 0; $progress -lt $ClassNames.Count; $progress++) {
        
        $Class_Name = $ClassNames[$progress]

        Write-Host "Compiling $Class_Name [$([string]($progress + 1) + "/" + $ClassNames.Count)]..."

        $Class_Constructors = Invoke-Expression "[$Class_Name].DeclaredConstructors" | Where-Object {$_.Name -eq ".ctor"}
        $Class_Methods = Invoke-Expression "[$Class_Name].DeclaredMethods" | Where-Object {!($_.Name.Contains("get_") -or $_.Name.Contains("set_"))}
        $Class_Properties = Invoke-Expression "[$Class_Name].DeclaredProperties"

        [string[]]$Class_Namespaces_CSharp = @("using System.Collections;", "using System.Management.Automation;", "using System.Text;")

        [string]$Class_Constructors_CSharp = ""
        [string]$Class_Methods_CSharp = ""
        [string]$Class_Properties_CSharp = ""

        [string]$Class_Handler_CSharp = ""
        [string]$Class_Static_Handler_CSharp = ""

        [string]$Class_CreatePowershell_CSharp = ""

        [hashtable[]]$Class_Handler_Properties = @()
        [hashtable[]]$Class_Static_Handler_Properties = @()

        [string]$Class_Powershell = ""
        [string]$References_Powershell = ""
        [string]$Class_Powershell_Basecode = $Content_Lines[(($Content_Lines | Select-String "class $($ClassNames[$progress]) ").LineNumber - 1)..($Content_Lines.Count - (($Content_Lines | Select-String "class $($ClassNames[$progress + 1]) ").LineNumber - 2))] -join "`n"


#Function References------------------------------------------------------------------------------
        
        $References_Powershell += '[string[]]$References_Module_b64 = @()' + "`n"

        if ($PSVersionTable.PSVersion -ge [Version]"6.0") {$InstalledModules = Get-Command -Module ((Get-ChildItem "C:\Program Files\WindowsPowerShell\Modules").Name | Where-Object {$_ -notin @("PackageManagement", "PowerShellGet")})} #Powershell Core and it sux balls
        else {$InstalledModules = Get-Command -Module (Get-InstalledModule | Where-Object {$_.Name -notin @("PackageManagement", "PowerShellGet")}).Name} #Windows Powershell

        foreach ($command in $InstalledModules) {

            if ($Class_Powershell_Basecode -match $command.Name) {
                if ($command.ModuleName -cin $ModuleReferences) {
                    
                    Write-Host " Adding $($command.ModuleName)..."

                    $module = Get-Item $command.Module.Path

                    $tmpfile = Join-Path $env:TMP ((New-Guid).Guid + ".zip")
                    Compress-Archive "$($module.DirectoryName)\*" $tmpfile -CompressionLevel Optimal -Force
                    $b64module = [Convert]::ToBase64String([File]::ReadAllBytes($tmpfile))
                    Remove-Item $tmpfile

                    $References_Powershell += '$References_Module_b64 += "' + $b64module + '"' + "`n"
                }
                else {

                    [int[]]$line = ($Content_Lines | Select-String $command.Name).LineNumber
                    Write-Warning "at $($line -join ", "): $($command.Name) from $($command.ModuleName) is not referenced!"
                }
            } 
        }

        $References_Powershell += '
foreach ($b64module in $References_Module_b64) {
 
    $tmpfile = Join-Path $env:TMP ((New-Guid).Guid + ".zip")
    $tmpfolder = $tmpfile.Replace(".zip", "")
    Set-Content $tmpfile ([Convert]::FromBase64String($b64module)) -Encoding Byte
    Expand-Archive $tmpfile $tmpfolder
    Remove-Item $tmpfile
    (Get-Childitem "$tmpfolder\*.psd1").FullName | Foreach-Object {Import-Module ([string]$_)}
}'


#Powershell Class---------------------------------------------------------------------------------

        $Class_Powershell += (($Content_Lines | Select-String @("using namespace", "using module", "using assembly") -SimpleMatch) -join "`n") + "`n"
        $Class_Powershell += $Class_Powershell_Basecode
        $Class_Powershell += "`n`n" + '$Global:Class = [' + "$Class_Name]`n"
        $Class_Powershell += '$Global:Class_Constructed = $null' + "`n`n"

#Class Constructors-------------------------------------------------------------------------------
        
        foreach ($constructor in $Class_Constructors) {

            $Class_Constructors_CSharp += " public $Class_Name ("
            $Class_Constructors_CSharp += "$([string]($constructor.GetParameters() -join ", ")))`n {`n "
            $Class_Constructors_CSharp += "object[] Arguments = { $([string]($constructor.GetParameters().Name -join ", ")) };`n`n "
            $Class_Constructors_CSharp += "_ClassHandler($('"' + $Class_Name + '"'), Arguments);"
            $Class_Constructors_CSharp += "`n }`n`n"
        }

#Class Methods------------------------------------------------------------------------------------

        foreach ($method in $Class_Methods) {
            
            [string]$CsNamespace = ""
            
            $CsNamespace = "using $($method.ReturnType.Namespace);"
            if (!$Class_Namespaces_CSharp.Contains($CsNamespace)) {$Class_Namespaces_CSharp += $CsNamespace}

            $Class_Methods_CSharp += " "

            switch ($method) {
    
                {$_.IsPublic} {$Class_Methods_CSharp += "public "}
                {$_.IsPrivate} {$Class_Methods_CSharp += "private "}
                {$_.IsStatic} {$Class_Methods_CSharp += "static "}
            }
            $Class_Methods_CSharp += "$($method.ReturnType.Name) "
            $Class_Methods_CSharp += "$($method.Name) ("
            $Class_Methods_CSharp += "$([string]($method.GetParameters() -join ", ")))`n {`n "
            $Class_Methods_CSharp += "object[] Arguments = { $([string]($method.GetParameters().Name -join ", ")) };`n`n "
            if ($method.IsStatic) {
                if ($method.ReturnType.Name -eq "void") {$Class_Methods_CSharp += "_StaticClassHandler($('"' + $method.Name + '"'), Arguments);"}
                else {$Class_Methods_CSharp += "return ($($method.ReturnType.Name))_StaticClassHandler($('"' + $method.Name + '"'), Arguments);"}
            }
            else {
                if ($method.ReturnType.Name -eq "void") {$Class_Methods_CSharp += "_ClassHandler($('"' + $method.Name + '"'), Arguments);"}
                else {$Class_Methods_CSharp+= "return ($($method.ReturnType.Name))_ClassHandler($('"' + $method.Name + '"'), Arguments);"}
            }
            $Class_Methods_CSharp += "`n }`n`n"
            $Class_Methods_CSharp = $Class_Methods_CSharp.Replace("Void", "void")
        }

#Class Properties---------------------------------------------------------------------------------

        foreach ($property in $Class_Properties) {

            $CsNamespace = "using $($property.PropertyType.Namespace);"
            if (!$Class_Namespaces_CSharp.Contains($CsNamespace)) {$Class_Namespaces_CSharp += $CsNamespace}

            [string]$CsProperty = ""

            ($Class_Powershell_Basecode.Split("`n") | Select-String "hidden" -SimpleMatch).LineNumber | ForEach-Object {
                if ($_ -and ([string](($Content_Lines[($_ - 1)..$Content_Lines.Count] | Select-String "\$")[0]) -match ("$" + $property.Name))) {
                    $Class_Properties_CSharp += " private "
                }
                else {$Class_Properties_CSharp += " public "}
            }
            if ($property.GetMethod.IsStatic -or $property.SetMethod.IsStatic) {
            
                $Class_Properties_CSharp += "static "

                $Class_Static_Handler_Properties += @{$property.Name = $property.PropertyType.Name}
            }
            else {$Class_Handler_Properties += @{$property.Name = $property.PropertyType.Name}}

            $Class_Properties_CSharp += "$($property.PropertyType.Name) $($property.Name) { get; set; }`n`n"
        }

#_ClassHandler------------------------------------------------------------------------------------

        if ($DebugMode) {$Class_Handler_CSharp += "`n public "}
        else {$Class_Handler_CSharp += "`n private "}

        $Class_Handler_CSharp += 'object _ClassHandler(string Method, object[] Arguments)
    {
        if (_Powershell.Commands.Commands.Count == 0) { CreatePowershell(); }
 
        string Script = Encoding.UTF8.GetString(Convert.FromBase64String("cGFyYW0gKFtzdHJpbmddJE1ldGhvZCwgW29iamVjdFtdXSRBcmd1bWVudHMsIFtoYXNodGFibGVdJFByb3BlcnRpZXMpDQpDbGFzc0hhbmRsZXIgJE1ldGhvZCAkQXJndW1lbnRzICRQcm9wZXJ0aWVzICRmYWxzZQ=="));
 
        Hashtable Properties = new Hashtable();'

        foreach ($property in $Class_Handler_Properties) {
        
            $Class_Handler_CSharp += "`n Properties.Add($('"' + $property.Keys + '"'), $($property.Keys));`n"
        }
        $Class_Handler_CSharp += '
        _Powershell.Commands.Commands.Clear();
        _Powershell.AddScript(Script);
        _Powershell.AddArgument(Method);
        _Powershell.AddArgument(Arguments);
        _Powershell.AddArgument(Properties);
 
        Hashtable returnproperties = _Powershell.Invoke()[0].BaseObject as Hashtable;
    '

        foreach ($property in $Class_Handler_Properties) {
        
            $Class_Handler_CSharp += "`n $($property.Keys) = ($($property.Values))returnproperties[$('"' + $property.Keys + '"')];"
        }
        $Class_Handler_CSharp += '
         
        foreach (InformationRecord message in _Powershell.Streams.Information)
        {
            Console.WriteLine(message);
        }
        foreach (WarningRecord message in _Powershell.Streams.Warning)
        {
            Console.WriteLine("WARNING: {0}", message);
        }
        _Powershell.Streams.ClearStreams();
 
        '

        $Class_Handler_CSharp += "return returnproperties" + '["RETURN"];' + "`n }`n`n"

#_StaticClassHandler------------------------------------------------------------------------------

        if ($DebugMode) {$Class_Static_Handler_CSharp += "`n public "}
        else {$Class_Static_Handler_CSharp += "`n private "}

        $Class_Static_Handler_CSharp += 'static object _StaticClassHandler(string Method, object[] Arguments)
    {
        if (_Powershell.Commands.Commands.Count == 0) { CreatePowershell(); }
 
        string Script = Encoding.UTF8.GetString(Convert.FromBase64String("cGFyYW0gKFtzdHJpbmddJE1ldGhvZCwgW29iamVjdFtdXSRBcmd1bWVudHMsIFtoYXNodGFibGVdJFByb3BlcnRpZXMpDQpDbGFzc0hhbmRsZXIgJE1ldGhvZCAkQXJndW1lbnRzICRQcm9wZXJ0aWVzICR0cnVl"));
 
        Hashtable Properties = new Hashtable();'

        foreach ($property in $Class_Static_Handler_Properties) {
        
            $Class_Static_Handler_CSharp += "`n Properties.Add($('"' + $property.Keys + '"'), $($property.Keys));`n"
        }
        $Class_Static_Handler_CSharp += '
        _Powershell.Commands.Commands.Clear();
        _Powershell.AddScript(Script);
        _Powershell.AddArgument(Method);
        _Powershell.AddArgument(Arguments);
        _Powershell.AddArgument(Properties);
 
        Hashtable returnproperties = _Powershell.Invoke()[0].BaseObject as Hashtable;
    '

        foreach ($property in $Class_Static_Handler_Properties) {
        
            $Class_Static_Handler_CSharp += "`n $($property.Keys) = ($($property.Values))returnproperties[$('"' + $property.Keys + '"')];"
        }
        $Class_Static_Handler_CSharp += '
         
        foreach (InformationRecord message in _Powershell.Streams.Information)
        {
            Console.WriteLine(message);
        }
        foreach (WarningRecord message in _Powershell.Streams.Warning)
        {
            Console.WriteLine("WARNING: {0}", message);
        }
        _Powershell.Streams.ClearStreams();
 
        '

        $Class_Static_Handler_CSharp += "return returnproperties" + '["RETURN"];' + "`n }`n"

#CreatePowershell---------------------------------------------------------------------------------
        
        if ($DebugMode) {$Class_CreatePowershell_CSharp += "public "}
        else {$Class_CreatePowershell_CSharp += "private "}

        $Class_CreatePowershell_CSharp += 'static void CreatePowershell()
    {
        string PlainRef = Encoding.UTF8.GetString(Convert.FromBase64String(_Base64Ref));
        string PlainClass = Encoding.UTF8.GetString(Convert.FromBase64String(_Base64Class));
        string ClassHandler = Encoding.UTF8.GetString(Convert.FromBase64String("JEdsb2JhbDpGaXJzdFJ1biA9ICR0cnVlDQokR2xvYmFsOkZpcnN0UnVuX1N0YXRpYyA9ICR0cnVlDQpmdW5jdGlvbiBDbGFzc0hhbmRsZXIgKFtzdHJpbmddJE1ldGhvZCwgW29iamVjdFtdXSRBcmd1bWVudHMsIFtoYXNodGFibGVdJFByb3BlcnRpZXMsIFtib29sXSRJc1N0YXRpYykgew0KICAgIA0KICAgIGZ1bmN0aW9uIFJldHVybkhhbmRsZXIgKFtvYmplY3RdJFJldHVyblZhbHVlLCBbc3RyaW5nW11dJEV4aXN0aW5nUHJvcGVydGllcywgW3N3aXRjaF0kSXNTdGF0aWMpIHsNCg0KICAgICAgICAkdGFibGUgKz0gQHtSRVRVUk4gPSAkUmV0dXJuVmFsdWV9DQoNCiAgICAgICAgaWYgKCRJc1N0YXRpYykgew0KICAgICAgICAgICAgZm9yZWFjaCAoJHByb3BlcnR5IGluICRFeGlzdGluZ1Byb3BlcnRpZXMpIHsNCiAgICAgICAgICAgICAgICAkdGFibGUgKz0gQHskcHJvcGVydHkgPSAkR2xvYmFsOkNsYXNzOjokcHJvcGVydHl9DQogICAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgICAgZWxzZSB7DQogICAgICAgICAgICBmb3JlYWNoICgkcHJvcGVydHkgaW4gJEV4aXN0aW5nUHJvcGVydGllcykgew0KICAgICAgICAgICAgICAgICR0YWJsZSArPSBAeyRwcm9wZXJ0eSA9ICRHbG9iYWw6Q2xhc3NfQ29uc3RydWN0ZWQuJHByb3BlcnR5fQ0KICAgICAgICAgICAgfQ0KICAgICAgICB9DQogICAgICAgIHJldHVybiAkdGFibGUNCiAgICB9DQoNCiAgICBpZiAoJElzU3RhdGljKSB7DQogICAgICAgIA0KICAgICAgICBpZiAoKCRHbG9iYWw6Rmlyc3RSdW5fU3RhdGljIC1hbmQgIVtzdHJpbmddOjpJc051bGxPckVtcHR5KCRQcm9wZXJ0aWVzLlZhbHVlcykpIC1vciAhJEdsb2JhbDpGaXJzdFJ1bl9TdGF0aWMpIHsNCiAgICAgICAgICAgIGZvcmVhY2ggKCRwcm9wZXJ0eSBpbiAkUHJvcGVydGllcy5LZXlzKSB7DQogICAgICAgICAgICAgICAgJEdsb2JhbDpDbGFzczo6KCRwcm9wZXJ0eSkgPSAkUHJvcGVydGllc1skcHJvcGVydHldDQogICAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgICAgJEdsb2JhbDpGaXJzdFJ1bl9TdGF0aWMgPSAkZmFsc2UNCg0KICAgICAgICAkcmV0dXJudmFsdWUgPSAkR2xvYmFsOkNsYXNzOjokTWV0aG9kLkludm9rZSgkQXJndW1lbnRzKQ0KICAgICAgICBSZXR1cm5IYW5kbGVyICRyZXR1cm52YWx1ZSAkUHJvcGVydGllcy5LZXlzIC1Jc1N0YXRpYw0KICAgIH0NCiAgICBlbHNlaWYgKCRNZXRob2QgLWVxICRHbG9iYWw6Q2xhc3MuTmFtZSkgew0KDQogICAgICAgICRHbG9iYWw6Q2xhc3NfQ29uc3RydWN0ZWQgPSBOZXctT2JqZWN0ICRHbG9iYWw6Q2xhc3MuTmFtZSAkQXJndW1lbnRzDQoNCiAgICAgICAgUmV0dXJuSGFuZGxlciAiTlVMTCIgJFByb3BlcnRpZXMuS2V5cw0KICAgIH0NCiAgICBlbHNlIHsNCiAgICAgICAgaWYgKCgkR2xvYmFsOkZpcnN0UnVuIC1hbmQgIVtzdHJpbmddOjpJc051bGxPckVtcHR5KCRQcm9wZXJ0aWVzLlZhbHVlcykpIC1vciAhJEdsb2JhbDpGaXJzdFJ1bikgew0KICAgICAgICAgICAgZm9yZWFjaCAoJHByb3BlcnR5IGluICRQcm9wZXJ0aWVzLktleXMpIHsNCiAgICAgICAgICAgICAgICAkR2xvYmFsOkNsYXNzX0NvbnN0cnVjdGVkLigkcHJvcGVydHkpID0gJFByb3BlcnRpZXNbJHByb3BlcnR5XQ0KICAgICAgICAgICAgfSAgDQogICAgICAgIH0NCiAgICAgICAgJEdsb2JhbDpGaXJzdFJ1biA9ICRmYWxzZSAgIA0KICAgICAgICANCiAgICAgICAgJHJldHVybnZhbHVlID0gJEdsb2JhbDpDbGFzc19Db25zdHJ1Y3RlZC4kTWV0aG9kLkludm9rZSgkQXJndW1lbnRzKQ0KICAgICAgICBSZXR1cm5IYW5kbGVyICRyZXR1cm52YWx1ZSAkUHJvcGVydGllcy5LZXlzDQogICAgfQ0KfQ=="));
 
        _Powershell.AddScript(PlainRef);
        _Powershell.AddScript(PlainClass);
        _Powershell.AddScript(ClassHandler);
        _Powershell.Invoke();
    }'
 + "`n`n"

#Finishing CSharp Class---------------------------------------------------------------------------
        
        $Class_Csharp += "`n`npublic class $Class_Name`n{`n "
        $Class_Csharp += "private static PowerShell _Powershell = PowerShell.Create();`n`n "
        if ($DebugMode) {$Class_Csharp += "public static object GetPowershell() { return _Powershell; }`n`n "}
        if ($DebugMode) {$Class_Csharp += "public "}
        else {$Class_Csharp += "private "}
        $Class_Csharp += 'static string _Base64Class = "' + [Convert]::ToBase64String([Encoding]::UTF8.GetBytes($Class_Powershell)) + '";' + "`n`n "
        if ($DebugMode) {$Class_Csharp += "public "}
        else {$Class_Csharp += "private "}
        $Class_Csharp += 'static string _Base64Ref = "' + [Convert]::ToBase64String([Encoding]::UTF8.GetBytes($References_Powershell)) + '";' + "`n`n "
        $Class_Csharp += $Class_CreatePowershell_CSharp

        $Class_Csharp += $Class_Static_Handler_CSharp
        $Class_Csharp += $Class_Handler_CSharp
        
        $Class_Csharp += " //Visible Class`n`n"

        $Class_Csharp += $Class_Constructors_CSharp
        $Class_Csharp += $Class_Properties_CSharp
        $Class_Csharp += $Class_Methods_CSharp
        $Class_Csharp += "}"
    }

    $Class_Csharp = ($Class_Namespaces_CSharp -join "`n") + $Class_Csharp

#Writing Class_CSharp-----------------------------------------------------------------------------
    
    if ($OutputAssembly.Contains(".dll")) {
    
        Add-Type -TypeDefinition $Class_Csharp -OutputAssembly $OutputAssembly | Out-Null
    }
    elseif ($OutputAssembly.Contains(".cs")) {
        
        New-Item -Path $OutputAssembly -Value $Class_Csharp | Out-Null
    }

    Write-Host "`nSucesss, $((Get-Item $OutputAssembly).Name) has been compiled!"
}