Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Write/Run your C# code in a .btm file

Aug
2,799
144
This is a not ready for prime time example of how to Write/Run your C# code in a .btm file.

Use it as a starting point for your own C# code.

Code:
:: Demonstrates how to use C# code from TCC via PSHELL
::
:: Compile the C# code in-memory
:: and/or
:: Compile the C# code to a .dll
::
:: Call the C# functions via PShell
::
@setlocal
@echo off

:: If useDLL=Y then use @name[%_batchname].dll
:: If useDLL=N then use @name[%_batchname].cs
set useDLL=Y

:: If rebuildDLL=Y then rebuild @name[%_batchname].dll from @name[%_batchname].cs
:: If rebuildDLL=N then use the existing @name[%_batchname].dll
set rebuildDLL=N

set theDLL=%@name[%_batchname].dll

set theCS=%@name[%_batchname].cs
set theDLLDir=%@path[%_batchname]

iff not isdir %theDLLDir then
  echo %theDLLDir does not exist.
  quit
endiff

::
:: Generate the C# Code
::
Gosub CSCode

::
:: Rebuild DLL
::
iff %RebuildDLL eq Y .and. exist %theDLLDir\%theDLL then
  del %theDLLDir\%theDLL /q
  Gosub BuildDLL
endiff

iff %RebuildDLL eq Y .and. not exist %theDLLDir\%theDLL then
  Gosub BuildDLL
endiff

::
:: UseDLL
::
iff %useDLL eq Y then
  iff not exist %theDLLDir\%theDLL then
    Gosub BuildDLL
    Gosub LoadDLL
  else
    :: theDLL exists, so just LoadDLL 
    Gosub LoadDLL 
  endiff
endiff

iff %useDLL eq N .and. defined theCS .and. exist %theDLLDir\%theCS then
  :: Load the C# code
  :: NOTE: This LOCKS the Type (Code) in Memory.
  :: To remove, exit TCC and re-start TCC
  pshell /s "add-type -path %theDLLDir\%theCS"
  set InternalExitCode=%_?
  set ExternalExitCode=%?

  iff %InternalExitCode ne 0 .or. %ExternalExitCode ne 0 then
  echo External Command Exit Code: %ExternalExitCode
  echo Internal Command Exit Code: %InternalExitCode
  echo.
  echo %theDLLDir\%theCS exists,
  echo but there is a problem with the %theDLLDir\%theCS source code.
  echo.
  echo File %_batchname near line %_batchline
  quit
  endiff

  unset InternalExitCode
  unset ExternalExitCode
endiff

iff %useDLL eq N .and. not exist %theDLLDir\%theCS then
  echo Problem at Line: %_batchline
  echo %theDLLDir\%theCS does not exist.
  quit
endiff

::
:: Execute a Function contained in the assembly
::
echo Days in month (%_year %_month) %@pshell[[Utils]::DaysInMonth(%_year,%_month)]

pshell /s "[Utils]::TestLoop(1000)"

:: set results=%@pshell[[Utils]::StringBuilderTest()]
:: set results

function GetUserName=`%@pshell[[advapi32]::GetUserName()]`
function DaysInMonth=`%@pshell[[utils]::DaysInMonth(%1,%2)]`

echo GetUserName: %@GetUserName[]

echos GetUserName:` `
pshell /s "[advapi32]::GetUserName()"

echos ShellTray:` `
pshell /s "[user32]::ShellTray()"

echo Days in month (%_year %_month) %@DaysInMonth[%_year, %_month]

echo Lambda: %@pshell[[utils]::SortNumbers("24 +13+12 +21 +46 +14+10+20+14+   30     +18+ 50")]

tmp /c tmp9:
pshell /s "$PSVersionTable" |! tpipe /simple=11 /output=tmp9:
type tmp9:

iff %UseDLL eq N then
  iff defined theCS then
    if exist %theDLLDir\%theCS del /q %theDLLDir\%theCS
  endiff
endiff

endlocal
quit

::----------------------------------------
:BuildDLL
:: The Microsoft (R) Visual C# Compiler version 4.14.0-3.25412.6 (c167adef) compiler
::   is required to build the .dll
::
:: U:\>which csc
:: csc is an alias : "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\csc.exe"
csc -platform:x64 -target:library %theDLLDir\%@name[%_batchname].cs
set InternalExitCode=%_?
set ExternalExitCode=%?

iff %InternalExitCode ne 0 .or. %ExternalExitCode ne 0 then
  echo External Command Exit Code: %ExternalExitCode
  echo Internal Command Exit Code: %InternalExitCode
  echo.
  if defined theCS echo Error compiling %theDLLDir\%theCS
  if defined theVB echo Error compiling %theDLLDir\%theVB
  echo.
  echo File %_batchname near line %_batchline
  quit
endiff

unset InternalExitCode
unset ExternalExitCode

describe %theDLLDir\%theDLL /D"csc -platform:x64 -target:library %theDLLDir\%theCS"
Return

::----------------------------------------
:LoadDLL
::
:: Load the C# code into memory
::
:: NOTE: Once you load the C# code into memory,
::   using LoadFrom(),
::   it can only be unloaded by exiting TCC
::
:: pshell /s "[System.Reflection.Assembly]::LoadFrom('%theDLLDir\%theDLL')"
::
:: I'm using .NET Framework 4.8
::
:: https://stackoverflow.com/a/37468429/14824505
:: Load the assembly without locking it during the duration of the PowerShell session
pshell /s "$bytes = [System.IO.File]::ReadAllBytes('%theDLLDir\%theDLL')"
pshell /s "[System.Reflection.Assembly]::Load($bytes)" > nul
Return

:CSCode
type <<- endtext > %theDLLDir\%theCS
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;

public class Utils
{
    public static long DaysInMonth(long year, long month)
    {
        int daysInMonth = DateTime.DaysInMonth((int)year, (int)month);
        return daysInMonth;
    }

    public static string StringBuilderTest()
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= 1000; i++)
        {
            builder.Append("Step " + i + "\r\n");
        }
        return builder.ToString();
    }

    public static long TestLoop(long A)
    {
        long index;
        for (index = 1; index <= A; index++)
        {
            // no-op
        }
        return index;
    }

    public static long Squared(long A)
    {
        long squared = A * A;
        return squared;
    }
    
    public static string SortNumbers(string input)
    {
    //string input = "82 +13+12 +21 +46 +14+10+20+14+   30     +18+ 50";

    var sortedNumbers = input
        .Split('+')
        .Select(s => int.Parse(s.Trim()))
        .OrderBy(n => n)
        .ToArray();

    return string.Join("+", sortedNumbers);
    }

}

public class advapi32
{
    public static string GetUserName()
    {
      StringBuilder buffer = new StringBuilder(256);
      int size = buffer.Capacity;

      bool ok = Win32API.GetUserName(buffer, ref size);

      if (!ok)
        return string.Empty;

      return buffer.ToString();
    }
}

public class user32
{
    public static long ShellTray()
    {
        return Win32API.FindWindow("Shell_TrayWnd", null);
    }
}

internal class Win32API
{
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool GetUserName(StringBuilder lpBuffer, ref int nSize);

    [DllImport("user32.dll", EntryPoint = "FindWindowA", CharSet = CharSet.Ansi)]
    public static extern long FindWindow(string lpClassName, string lpWindowName);
}
endtext
Return

Joe
 
Last edited:
Back
Top