嵌入到 .bat 文件中的 C# 源代码
C# source code embedded into .bat file
有人可能想知道 - 如何制作一个带有嵌入式 c# 代码的 .bat
文件来编译和执行它 'on-the-fly'?
是否可以在一个文件中同时包含批处理指令和 C# 代码?
是的,有可能。
这是一个例子:
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);
}
}
说明:这个批处理文件由两部分组成:首先是批处理代码,其次是c#代码。执行时,命令 shell 将忽略 c#-comments /*
和 */
作为错误行并仅执行批处理代码。由于批处理块末尾的退出命令,执行永远不会到达 c# 代码。
文件的批处理部分搜索 csc.exe
(.NET 编译器)。找到后,批处理文件将自身传递给 csc.exe
以编译 c# 代码。由于注释(/* 和 */),批处理部分被忽略,只有 c# 部分将被编译。编译后生成.exe文件执行,执行后删除
编辑:2> nul
将标准错误(描述符 2)重定向到 null 以抑制 'not-found' 消息。
批处理代码可以注释掉,但是不能避免注释被批处理为错误。
可以抑制错误本身,但至少会显示第一行。
/* 2>NUL
@echo off
...
start the C# code
*/
...
C#-Code
由于 C# 似乎不接受 :@
字符之一作为文件中的第一个字符,因此我看不到完美解决方案的可能方法。
方法不多。
1) 与之前的答案类似,但自动检测最新版本的.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) 在 windows 10 中默认安装了 .net 框架,并且 msbuild 允许内联任务,这为内存编译提供了可能性(即- 没有额外的 exe 文件)和嵌入 xml,这意味着没有多余的输出。请注意,命令行参数保存在 CMD_ARGS
环境变量中:
<!-- :
@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) 使用 powershell 和 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) 再注意一点——如果你不使用 P/Invoke 可能更方便的是使用 JScript.net,它需要更少的代码并且有类似的语法,让您可以访问 .net 内容并且没有冗余输出:
@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")
有人可能想知道 - 如何制作一个带有嵌入式 c# 代码的 .bat
文件来编译和执行它 'on-the-fly'?
是否可以在一个文件中同时包含批处理指令和 C# 代码?
是的,有可能。
这是一个例子:
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);
}
}
说明:这个批处理文件由两部分组成:首先是批处理代码,其次是c#代码。执行时,命令 shell 将忽略 c#-comments /*
和 */
作为错误行并仅执行批处理代码。由于批处理块末尾的退出命令,执行永远不会到达 c# 代码。
文件的批处理部分搜索 csc.exe
(.NET 编译器)。找到后,批处理文件将自身传递给 csc.exe
以编译 c# 代码。由于注释(/* 和 */),批处理部分被忽略,只有 c# 部分将被编译。编译后生成.exe文件执行,执行后删除
编辑:2> nul
将标准错误(描述符 2)重定向到 null 以抑制 'not-found' 消息。
批处理代码可以注释掉,但是不能避免注释被批处理为错误。
可以抑制错误本身,但至少会显示第一行。
/* 2>NUL
@echo off
...
start the C# code
*/
...
C#-Code
由于 C# 似乎不接受 :@
字符之一作为文件中的第一个字符,因此我看不到完美解决方案的可能方法。
方法不多。
1) 与之前的答案类似,但自动检测最新版本的.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) 在 windows 10 中默认安装了 .net 框架,并且 msbuild 允许内联任务,这为内存编译提供了可能性(即- 没有额外的 exe 文件)和嵌入 xml,这意味着没有多余的输出。请注意,命令行参数保存在 CMD_ARGS
环境变量中:
<!-- :
@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) 使用 powershell 和 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) 再注意一点——如果你不使用 P/Invoke 可能更方便的是使用 JScript.net,它需要更少的代码并且有类似的语法,让您可以访问 .net 内容并且没有冗余输出:
@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")