简体   繁体   中英

C# source code embedded into .bat file

One may wonder - how to make a .bat file with embedded c# code to compile and execute it 'on-the-fly'?
Is it possible to have both batch-instructions and c# code in one file?

Yes, it is possible.
Here is an example:

Example.bat

/* 2> nul
@echo off && cls && echo Loading... && echo.
set WinDirNet=%WinDir%\Microsoft.NET\Framework
if exist "%WinDirNet%\v2.0.50727\csc.exe" set csc="%WinDirNet%\v2.0.50727\csc.exe"
if exist "%WinDirNet%\v3.5\csc.exe" set csc="%WinDirNet%\v3.5\csc.exe"
if exist "%WinDirNet%\v4.0.30319\csc.exe" set csc="%WinDirNet%\v4.0.30319\csc.exe"
if "%csc%" == "" ( echo .NET Framework not found! && echo. && pause && exit )
%csc% /nologo /out:"%~dpnx0.exe" "%~dpnx0"
if not "%ERRORLEVEL%" == "0" ( echo. && pause && exit )
cls
"%~dpnx0.exe" %*
del "%~dpnx0.exe"
exit
*/

using System;
class Program
{
    static void Main()
    {
        System.Console.WriteLine("Hello, World!\r\nI am at " + System.Environment.Version);
    }
}

Explanation : this batch-file consist of two parts: firstly a batch-code and secondary ac# code. When executing, command shell will ignore c#-comments /* and */ as error-lines and execute only batch-code. Due exit command at the end of batch-block, the execution never reach c# code.
Batch part of the file searches for csc.exe (.NET compiler). After found, batch file passes itself into csc.exe to compile c# code. Due to comments (/* and */) batch-part is ignored and only c# part will be compiled. After compilation generated .exe file is executed and deleted after execution.

Edit : 2> nul redirecting standard error (descriptor 2) to null to suppress 'not-found' message.

You can comment out the batch code, but you can't avoid that the comment is handled by batch as an error.

The error itself can be suppressed, but at least the first line will be shown.

/* 2>NUL
@echo off
...
start the C# code
*/
...
C#-Code

As C# seems not to accept one of the :@ characters as the first character in a file, I can't see a possible way for a perfect solution.

There are few ways.

1) Similar to the previous answers ,but with automatic detection the latest version of .net framework:

// 2>nul||@goto :batch
/*
@echo off
setlocal

:: find csc.exe
set "csc="
for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*csc.exe") do  set "csc=%%#"

if not exist "%csc%" (
   echo no .net framework installed
   exit /b 10
)

if not exist "%~n0.exe" (
   call %csc% /nologo /w:0 /out:"%~n0.exe" "%~dpsfnx0" || (
      exit /b %errorlevel% 
   )
)
%~n0.exe %*
endlocal & exit /b %errorlevel%

*/

using System;


class QE {
    static void Main(string[] args) {
         Console.WriteLine("Echo from C#");
    }
}

2) In windows 10 the .net framework is installed by default and there the msbuild allows inline tasks which gives a possibility for in-memory compilation (ie - no additional exe files) and emebedding in xml which means no redundant output. Note that command line arguments are saved in CMD_ARGS environment variable:

<!-- :
    @echo off


        echo -^- FROM BATCH

        set "CMD_ARGS=%*"
        ::::::  Starting C# code :::::::
        :: searching for msbuild location
        for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*msbuild.exe") do  set "msb=%%#"

        if not defined  msb (
           echo no .net framework installed
           exit /b 10
        )

        rem ::::::::::  calling msbuid :::::::::
        call %msb% /nologo  /noconsolelogger "%~dpsfnx0"
        rem ::::::::::::::::::::::::::::::::::::
        exit /b %errorlevel%

--> 


<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="_">
    <_/>
  </Target>
  <UsingTask
    TaskName="_"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll" > 

    <Task>
     <Reference Include="$(MSBuildToolsPath)\System.Windows.Forms.dll"/>
      <Using Namespace="System" />

      <Code Type="Method" Language="cs">
        <![CDATA[

      public override bool Execute(){
         MyMethod();
         return true;
      }

      void MyMethod(){
         Console.WriteLine("Whoa");
         String CMD_ARGS=Environment.GetEnvironmentVariable("CMD_ARGS");
            System.Console.WriteLine("Echo from C# with msBuild -- "+"$(MSBuildToolsVersion)"); 
      }
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

3) With powershell and Add-Type:

<# : batch portion
@echo off & setlocal

set "CMD_ARGS=%~1"

powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF

: end batch / begin powershell #>

param($psArg1 = $env:psArg1)


$CS = @" 
namespace PS {
  public class CS
  {
    public static void csEcho(string arg)
    { System.Console.WriteLine("echo from C# " + arg); }
  }
}
"@

Add-Type -TypeDefinition $CS -Language CSharp


[PS.CS]::csEcho($psArg1 + " and PowerShell")

4) One more note - if you're not using P/Invoke may more convenient will be to use JScript.net which require less code and has similar syntax and give you access to the .net stuff and has no redundant output:

@if (@X)==(@Y) @end /* JScript comment
@echo off
setlocal

for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\*jsc.exe"') do (
   set "jsc=%%v"
)

if not exist "%~n0.exe" (
    "%jsc%" /nologo /out:"%~n0.exe" "%~dpsfnx0"
)

%~n0.exe %*

endlocal & exit /b %errorlevel%


*/

import System;
Console.Write("Echo from .NET")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM