.net 如何将PowerShell脚本打包到可执行文件中并作为Windows服务运行?

de90aj5v  于 2022-12-20  发布在  .NET
关注(0)|答案(1)|浏览(290)

因此,我尝试运行PowerShell脚本作为一个窗口服务,我已经尝试了不同的解决方案,但我有一个问题,PowerShell脚本文件不应该提供给管理员,我不能利用第三方库或类似的。它是风险暴露运行脚本本身,而不是暴露.exe文件,给管理员。我尝试过这个解决方案:https://learn.microsoft.com/en-us/archive/msdn-magazine/2016/may/windows-powershell-writing-windows-services-in-powershell
我现在正在尝试使用这个(https://www.janhendrikpeters.de/2021/01/15/developing-a-windows-service-in-powershell/)解决方案,请参见代码。
通过$OnStart变量将我的PowerShell脚本传递给脚本和服务代码可以工作,但PowerShell脚本需要在服务器上可用。本质上,我希望我的PS代码包含在已编译的exe文件中。因此,我尝试在.NET部分之前制作一个“$OnStart”代码块,将其转换为字符串,并让.NET代码启动此代码。然而,这不起作用,因为我在我的PowerShell代码中使用了“引号,是的,我不能将“更改为”,因为我的脚本中的某些函数需要双引号。我还没有尝试过的解决方案是在脚本开始时创建一个文件,并将我的PowerShell脚本写入该文件,使用Addscript函数与此文件的路径,运行脚本,并且在脚本已经开始删除文件之后。
这是一个可行的选择吗?
或者,是否有办法将包含“”的PowerShell代码脚本块传递到“AddScript”函数中?

param
(
    [string]
    $ServiceName = 'InfraSvc',

    [string]
    $OutPath = $pwd.Path,

    [string]
    $OnStart,

    [string]
    $OnStop,

    [switch]
    $Register
)

$binPath = (Join-Path -Path $OutPath -ChildPath "$ServiceName.exe")
if (Test-Path -Path $binPath)
{
    Remove-Item -Path $binPath
}

Add-Type -TypeDefinition @"
using System;
using System.ServiceProcess;
using System.Management.Automation;

public static class HostProgram
{
    #region Nested classes to support running as service
    public const string ServiceName = "$ServiceName";

    public class Service: ServiceBase
    {
        public Service()
        {
            ServiceName = HostProgram.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            HostProgram.Start(args);
        }

        protected override void OnStop()
        {
            HostProgram.Stop();
        }
    }
    #endregion

    static void Main(string[] args)
    {
        if (!Environment.UserInteractive)
            // running as service
            using (var service = new Service())
                ServiceBase.Run(service);
        else
        {
            // running as console app
            Start(args);

            Console.WriteLine("Press any key to stop...");
            Console.ReadKey(true);

            Stop();
        }
    }

    private static void Start(string[] args)
    {
        // service startup code here
        string onStart = @"$OnStart";

        if (string.IsNullOrWhiteSpace(onStart)) return;
        using (var psh = PowerShell.Create())
        {
            psh.AddScript((System.IO.File.ReadAllText(onStart)));
            psh.Invoke();
        }
    }

    private static void Stop()
    {
        // service startup code here
        string onStop = @"$OnStop";

        if (string.IsNullOrWhiteSpace(onStop)) return;
        using (var psh = PowerShell.Create())
        {
            psh.AddScript((System.IO.File.ReadAllText(onStop)));
            psh.Invoke();
        }
    }
}
"@ -OutputAssembly $binPath -ReferencedAssemblies System.ServiceProcess, System.Management.Automation

if ($Register.IsPresent)
{
    New-Service -Name $ServiceName -BinaryPathName $binPath -StartupType Automatic
}
t98cgbkg

t98cgbkg1#

我的主要问题是在powershell中编写一个windows服务,将其制作成一个.exe文件,以便在安装后“隐藏”代码和配置,并从Services. msc运行它。
我设法解决了这些问题,但我不会说这是一个美丽的解决方案。每一个问题都是在同一个脚本中解决的。
1.使用iExpress将脚本打包到“.exe”文件中
1.在.net中制作一个非常基本的windows服务,用于启动和停止打包的脚本。
用于将脚本打包到SED文件中的Powershell代码:

$Text = 
'[Version]
Class=IEXPRESS
SEDVersion=3
[Options]
PackagePurpose=InstallApp
ShowInstallProgramWindow=1
HideExtractAnimation=0
UseLongFileName=1
InsideCompressed=0
CAB_FixedSize=0
CAB_ResvCodeSigning=0
RebootMode=N
InstallPrompt=%InstallPrompt%
DisplayLicense=%DisplayLicense%
FinishMessage=%FinishMessage%
TargetName=%TargetName%
FriendlyName=%FriendlyName%
AppLaunched=%AppLaunched%
PostInstallCmd=%PostInstallCmd%
AdminQuietInstCmd=%AdminQuietInstCmd%
UserQuietInstCmd=%UserQuietInstCmd%
SourceFiles=SourceFiles
[Strings]
InstallPrompt=
DisplayLicense=
FinishMessage=
TargetName='+$PackedExePath+'
FriendlyName=Filetransfer2
AppLaunched=powershell.exe -ExecutionPolicy Bypass -File '+$CurrentScriptName+'
PostInstallCmd=<None>
AdminQuietInstCmd=
UserQuietInstCmd=
FILE0="'+ $CurrentScriptName + '"
[SourceFiles]
SourceFiles0='+ $PSScriptRoot83 +'
[SourceFiles0]
%FILE0%=
'

Set-Content -Path $SedFilePath -Value $Text

#Checking if the file is ready to be run by iExpress
$isSEDOpen = Test-FileLock -Path $SedFilePath
while ($isSEDOpen) {
    #Write-host "Waiting for SED file to be ready for use"
    Start-Sleep(3)
    $isSEDOpen = Test-FileLock -Path $SedFilePath
}
if(-not $isSEDOpen) {
    #Write-host "$SedFilePath is ready for use"
}

C:\Windows\System32\iexpress.exe /N /Q $SedFilePath

因为我在“Program files”文件夹中安装了该服务,所以我必须将路径转换为8.3文件名才能使iExpress运行它。因此,$Text变量中用于生成iExpress打包powershellscript所需的. SED文件的所有变量都被转换。这是通过将“Program files”替换为“PROGRA~1”来完成的。代码示例:

$PSScriptRoot83 = $PSScriptRoot.replace('Program Files','PROGRA~1')

打包的powershell代码的路径将在Windows服务代码中使用,“$PackedExePath”. Windows服务代码用于启动和停止打包的powershell文件:

Add-Type -TypeDefinition @"
using System;
using System.ServiceProcess;
using System.Management.Automation;
using System.Diagnostics;

public static class HostProgram
{
    public const string ServiceName = "$ServiceName";
    public class Service : ServiceBase 
    {
        public Service() 
        {
            ServiceName = HostProgram.ServiceName;
        }

    protected override void OnStart(string[] args) 
    {
        HostProgram.Start(args);
        
    }

    protected override void OnStop() 
    {
        HostProgram.Stop();
    }

}

static void Main(string[] args) 
{

    if(!Environment.UserInteractive) 
        using (var service = new Service())
            ServiceBase.Run(service);
    else 
    {
        Start(args);

        Console.WriteLine("Press any key to stop..");
        Console.ReadKey(true);

        Stop();
    }
    
}

private static void Start(string[] args) 
{
    Console.WriteLine("Started");
    string exePath = @"$PackedExePath";
    Process.Start(exePath);
}

private static void Stop() 
{
    foreach (var process in Process.GetProcessesByName("$PackedexeName")) 
    {
        process.Kill();
    }
    Console.WriteLine("stopped");
}

}
"@ -OutputAssembly $binPath -ReferencedAssemblies System.ServiceProcess, System.Management.Automation
    if (Test-Path $binPath) {
        New-Service -Name $ServiceName -BinaryPathName $binPath -Description $ServiceDescription -StartupType Automatic
   }

可以在同一个脚本中执行此操作,但是您需要检查服务是否已经安装,以避免在每次启动服务时创建一个新的脚本包和一个新的windows服务。因此,上面显示的所有代码都包含在此if语句中:

try {

$GottenName = Get-Service -Name $ServiceName -ErrorAction Stop
} catch {}

if (-not ($GottenName.Name -match $ServiceName)){

#Previous code snippes here...

}

最好是在.Net中创建一个windows服务,但是如果你除了powershell没有其他选择,并且你的powershell代码需要打包,这是我能想到的最好的免费方法,我知道有一些工具可以打包你的powershell脚本,但是我负担不起。
值得一提的是,iExpress包含一些漏洞,但在此情况下不会造成任何风险。您可以阅读更多有关iExpress here中漏洞的信息。

相关问题