简体   繁体   中英

Customer refuses “scripts” in the environment. How do I embed a *.ps1 in a C# app?

I have the following sample Powershell script that is embedded in my C# application.

Powershell Code

    $MeasureProps = "AssociatedItemCount", "ItemCount", "TotalItemSize"

   $Databases = Get-MailboxDatabase -Status
    foreach($Database in $Databases) {

        $AllMBStats = Get-MailboxStatistics -Database $Database.Name    
     $MBItemAssocCount = $AllMBStats   |   %{$_.AssociatedItemCount.value} |  Measure-Object -Average   -Sum
     $MBItemCount =      $AllMBStats   |   %{$_.ItemCount.value} |  Measure-Object -Average  -Sum

        New-Object PSObject -Property @{
            Server = $Database.Server.Name
            DatabaseName = $Database.Name
            ItemCount = $MBItemCount.Sum
        }
    }

Visual Studio offers me the following embedding options:

在此输入图像描述

Every PowerShell sample I've seen (MSDN on Exchange , and MSFT Dev Center ) required me to chop up the Powershell command into "bits" and send it through a parser.

I don't want to leave lots of PS1 files with my application, I need to have a single binary with no other "supporting" PS1 file.

How can I make it so myapp.exe is the only thing that my customer sees?

Many customers are averse to moving away from a restricted execution policy because they don't really understand it. It's not a security boundary - it's just an extra hoop to jump through so you don't shoot yourself in the foot. If you want to run ps1 scripts in your own application, simply use your own runspace and use the base authorization manager which pays no heed to system execution policy:

InitialSessionState initial = InitialSessionState.CreateDefault();

// Replace PSAuthorizationManager with a null manager which ignores execution policy
initial.AuthorizationManager = new
      System.Management.Automation.AuthorizationManager("MyShellId");

// Extract psm1 from resource, save locally
// ...

// load my extracted module with my commands
initial.ImportPSModule(new[] { <path_to_psm1> });

// open runspace
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();

RunspaceInvoke invoker = new RunspaceInvoke(runspace);

// execute a command from my module
Collection<PSObject> results = invoker.Invoke("my-command");

// or run a ps1 script    
Collection<PSObject> results = invoker.Invoke("c:\temp\extracted\my.ps1");

By using a null authorization manager, execution policy is completed ignored. Remember - this is not some "hack" because execution policy is something for protecting users against themselves. It's not for protecting against malicious third parties.

http://www.nivot.org/nivot2/post/2012/02/10/Bypassing-Restricted-Execution-Policy-in-Code-or-in-Script.aspx

First of all you should try removing your customer's aversion To scripts. Read up about script signing, execution policy etc.

Having said that, you can have the script as a multiline string in C# code itself and execute it.Since you have only one simple script, this is the easiest approach.

You can use the AddScript ,ethos which takes the script as a string ( not script path)

http://msdn.microsoft.com/en-us/library/dd182436(v=vs.85).aspx

You can embed it as a resource and retrieve it via reflection at runtime. Here's a link from MSDN . The article is retrieving embedded images, but the principle is the same.

You sort of hovered the answer out yourself. By adding it as content, you can get access to it at runtime (see Application.GetResourceStream). Then you can either store that as a temp file and execute, or figure out a way to invoke powershell without the use of files.

Store your POSH scripts as embedded resources then run them as needed using something like the code from this MSDN thread :

public static Collection<PSObject> RunScript(string strScript)
{
  HttpContext.Current.Session["ScriptError"] = "";
  System.Uri serverUri = new Uri(String.Format("http://exchangsserver.contoso.com/powershell?serializationLevel=Full"));
  RunspaceConfiguration rc = RunspaceConfiguration.Create();
  WSManConnectionInfo wsManInfo = new WSManConnectionInfo(serverUri, SHELL_URI, (PSCredential)null);
  using (Runspace runSpace = RunspaceFactory.CreateRunspace(wsManInfo))
  {
    runSpace.Open();
    RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
        scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
    PowerShell posh = PowerShell.Create();
    posh.Runspace = runSpace;
    posh.AddScript(strScript);
    Collection<PSObject> results = posh.Invoke();
    if (posh.Streams.Error.Count > 0)
    {
      bool blTesting = false;
      string strType = HttpContext.Current.Session["Type"].ToString();
      ErrorRecord err = posh.Streams.Error[0];
      if (err.CategoryInfo.Reason == "ManagementObjectNotFoundException")
      {
    HttpContext.Current.Session["ScriptError"] = "Management Object Not Found Exception Error " + err + " running command " + strScript;
    runSpace.Close();
    return null;
      }
      else if (err.Exception.Message.ToString().ToLower().Contains("is of type usermailbox.") && (strType.ToLower() == "mailbox"))
      {
    HttpContext.Current.Session["ScriptError"] = "Mailbox already exists.";
    runSpace.Close();
    return null;
      }
      else
      {
    HttpContext.Current.Session["ScriptError"] = "Error " + err + "<br />Running command " + strScript;
    fnWriteLog(HttpContext.Current.Session["ScriptError"].ToString(), "error", strType, blTesting);
    runSpace.Close();
    return null;
      }
    }
    runSpace.Close();
    runSpace.Dispose();
    posh.Dispose();
    posh = null;
    rc = null;
    if (results.Count != 0)
    {
      return results;
    }
    else
    {
      return null;
    }
  }
}

The customer just can't see the PowerShell script in what you deploy, right? You can do whatever you want at runtime. So write it to a temporary directory--even try a named pipe, if you want to get fancy and avoid files--and simply start the PowerShell process on that.

You could even try piping it directly to stdin. That's probably what I'd try first, actually. Then you don't have any record of it being anywhere on the computer. The Process class is versatile enough to do stuff like that without touching the Windows API directly.

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