4

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:

enter image description here

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?

makerofthings7
  • 60,103
  • 53
  • 215
  • 448

6 Answers6

6

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

x0n
  • 51,312
  • 7
  • 89
  • 111
3

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

manojlds
  • 290,304
  • 63
  • 469
  • 417
  • I considered this, but I haven't been able to figure out how to make PowerShell do this. Do I use the AddScript method? I thought that was to an instance of a *.ps1 on the drive, not the verbatim script. – makerofthings7 Feb 11 '12 at 02:39
  • @makerofthings7 - My answer contains an example from MSDN of doing what you're after. – M.Babcock Feb 11 '12 at 02:43
  • Perhaps since I haven't seen "whitespace" in any sample using that method is what threw me off. I'll give it a shot! – makerofthings7 Feb 11 '12 at 02:45
  • Hmm didn't work for me. I make the script in my question equal to a local variable, and pass that to psHost.AddScript I get the error `Assignment statements are not allowed in restricted language mode or a Data section.` – makerofthings7 Feb 11 '12 at 02:58
1

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.

Daniel Mann
  • 57,011
  • 13
  • 100
  • 120
1

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.

Alxandr
  • 12,345
  • 10
  • 59
  • 95
1

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;
    }
  }
}
M.Babcock
  • 18,753
  • 6
  • 54
  • 84
  • I suppose I can modify this to do Application.GetResourceStream – makerofthings7 Feb 11 '12 at 02:47
  • I figured you wouldn't need an example of that. If necessary I can rework the code sample. – M.Babcock Feb 11 '12 at 02:47
  • Basically, you could just do `using(var s = Application.GetResourceStream(yada,yada)) using(var sr = new StreamReader(s)) RunScript(sr.ReadToEnd());` – Alxandr Feb 11 '12 at 02:50
  • When I make the above script equal to a local variable, and pass that to posh.AddScript I get the error `Assignment statements are not allowed in restricted language mode or a Data section.` – makerofthings7 Feb 11 '12 at 02:57
  • I believe you need to first run the `Set-ExecutionPolicy` cmdlet passing `Unrestricted` as a parameter. – M.Babcock Feb 11 '12 at 03:08
  • @makerofthings7 - I've updated the code above to change the execution policy to `Unrestricted`. Please let me know if this doesn't work. – M.Babcock Feb 11 '12 at 03:21
  • @M.Babcock If executionpolicy is controlled by enterprise group policy (as it usually is,) you cannot change it like this. – x0n Feb 11 '12 at 04:16
  • @x0n - So what is the solution then (beyond the OP telling their customer that they don't know what they're doing)? I don't work with POSH very often (and even rarer from C#) I just know that I'll be implementing something similar relatively soon (probably third quarter). Is there a proper solution for this problem? – M.Babcock Feb 11 '12 at 04:19
  • @x0n - It's actually above at the moment (the +1 was from me). I saw it and I think I get where you're going but it seems like you lose the dynamic execution of POSH scripts available in the code I provided (possibly by nature). I would think that would be a deal breaker in a lot of situations. May as well return to dynamic batch execution. :( – M.Babcock Feb 11 '12 at 05:42
  • @m.babcock You lose nothing with my code, and gain everything. Using AddScript will still fail at running ps1 files due to GPO enforced execution policy. My code could execute addscript - and as it stands, you can call invoker.Invoke("script") too so I'm not following you at all. The script you posted above is part of an asp.net page and is performing remote execution on an exchange endpoint. I don't even see how it's relevant to the question at hand, apart from the exchange element? – x0n Feb 11 '12 at 06:13
0

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.

Zenexer
  • 18,788
  • 9
  • 71
  • 77
  • The closest I've comes to Pipes in C# is WCF... is PInvoke required? I may want to hack on this scenario just because it's interesting – makerofthings7 Feb 11 '12 at 13:58