3

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?

3 Answers 3

4

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 a c# 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.

Sign up to request clarification or add additional context in comments.

5 Comments

You'll get '/*' is not recognized as an internal or external command, operable program or batch file. whenever you start the batch file, though :p
@Nyerguds I think, cls is supposed to take care of that :p
@Exerion Oh, I never doubted that. It's just that there's no real start sequence which is completely valid for both. You're just choosing the lesser of two evils since .bat doesn't care about a single illegal command, whereas the cs file wouldn't compile ;)
Ooh, I found one. Using /* 2> nul as first line will suppress the "not found" message: stackoverflow.com/q/11060166/395685 Sadly, since this is before the @echo off, it won't suppress the command showing though, so it'll still need the cls.
Ah, I added a little addendum to that. The cls is still kind of needed, on account of the command being before the @echo off, meaning it'll appear on screen. And you can't put an @ before it either, since that'll once again make it C#-invalid. But at least you won't worry users with an error message flashing on their console for a split second, I guess.
1

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.

Comments

0

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 (i.e. - 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")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.