NUnit Test on Jenkins fail (nunit xml result file is empty ) - c#

I recently started to build my C# Projects on a Jenkins build server.
But recently i had the problem that the reported NUnit xml is empty (the file is created but has no content.)
Console output is as follows
Process leaked file descriptors. See
http://wiki.jenkins-ci.org/display/JENKINS/Spawning+processes+from+build
for more information [WARNINGS] Parsing warnings in console log with
parser MSBuild [WARNINGS] Computing warning deltas based on reference
build #288 Recording NUnit tests results ERROR: Step ‘Publish NUnit
test result report’ aborted due to exception:
hudson.util.IOException2: Could not transform the NUnit report. Please
report this issue to the plugin author at
hudson.plugins.nunit.NUnitArchiver.invoke(NUnitArchiver.java:65) at
hudson.plugins.nunit.NUnitArchiver.invoke(NUnitArchiver.java:26) at
hudson.FilePath.act(FilePath.java:990) at
hudson.FilePath.act(FilePath.java:968) at
hudson.plugins.nunit.NUnitPublisher.perform(NUnitPublisher.java:145)
at hudson.tasks.BuildStepMonitor$1.perform(BuildStepMonitor.java:20)
at
hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:782)
at
hudson.model.AbstractBuild$AbstractBuildExecution.performAllBuildSteps(AbstractBuild.java:723)
at hudson.model.Build$BuildExecution.post2(Build.java:185) at
hudson.model.AbstractBuild$AbstractBuildExecution.post(AbstractBuild.java:668
i know that the problem are probably the leaked file descriptors, but i am not really sure how to fix that.
the NUnit tests are executed with a powershell script, that grabs all necessary dlls
powershell script:
param(
[string] $sourceDirectory = "./trunk/TestProjects/"
, $fileFilters = #("*UnitTest*.dll")
, [string]$filterText = "*\bin*"
)
#script that executes all unit tests available.
Write-Host "Source: $sourceDirectory"
Write-Host "File Filters: $fileFilters"
Write-Host "Filter Text: $filterText"
$cFiles = ""
$nUnitExecutable = "C:\Program Files (x86)\NUnit.org\nunit-console\nunit3-console.exe"
# look through all subdirectories of the source folder and get any unit test assemblies. To avoid duplicates, only use the assemblies in the bin folder
[array]$files = get-childitem $sourceDirectory -include $fileFilters -recurse | select -expand FullName | where {$_ -like $filterText}
foreach ($file in $files)
{
$cFiles = $cFiles + '"' + $file + '"' + " "
}
# set all arguments and execute the unit console
$argumentList = #("$cFiles", "--result=nunit-result.xml;format=nunit2","--framework=net-4.5","--process=Single")
$unitTestProcess = start-process -filepath $nUnitExecutable -argumentlist $argumentList -nonewwindow
echo "$nUnitExecutable $argumentList"
$exitCode = $unitTestProcess.ExitCode
exit $exitCode
this problem only happens if the script is executed via jenkins
#######################UPDATE
after some investigation i found out that his only happens when i add 1 testcase in which wpf controls are created by invoking them on the UI thread.
[Test Apartment(ApartmentState.STA) RunInApplicationDomain]
public void CheckPluginModel()
{
var app = Application.Current ?? new Application { ShutdownMode = ShutdownMode.OnExplicitShutdown };
PluginModel model = new PluginModel();
var task= model.LoadPluginsFromPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
RunApplication(() => task.IsCompleted);
Assert.That(model.AvailablePluginControls.Count, Is.EqualTo(1));
Assert.That(model.Workflows.Count, Is.EqualTo(1));
Console.WriteLine("Plugin model check finished");
}
RunApplication:
/// <summary>
/// Runs the application. as long as abortCriteria returns false
/// </summary>
/// <param name="abortCriteria">The abort criteria.</param>
private void RunApplication(Func<bool> abortCriteria, int duetime = 100, int period=100)
{
Console.WriteLine("Application started");
System.Threading.Timer timer = null;
timer = new Timer((obj) =>
{
if (abortCriteria.Invoke())
{
Application.Current.Dispatcher.Invoke(() => Application.Current.Shutdown());
timer.Dispose();
}
}, null, duetime, period);
Application.Current.Run();
Console.WriteLine("Application stopped");
}
All GUI elements are created
await Application.Current.Dispatcher.BeginInvoke(new Action(() => AvailablePluginControls.Add((APControl)Activator.CreateInstance(item))),null);

Nunit Plugin in Jenkins is not support Nunit 3 xml format. I also the similar problem on Jenkins. I used to convert Nunit 3 result format to Nunit 2 format.
"C:\NUnit 3.5.0\nunit3-console.exe" /result:\MyApplication.xml;format=nunit2 "D:\Jenkins\workspace\MyApplication.Tests.dll"

The problem was as followed:
The unit tests are executed via the powershell script.
The powershell start process does not wait untill the process has finished. This causes the problem that a child process is not finished.
in order for start-process to wait until the process has finished, the flag -Wait has to be added.

Related

Why does my powershell function fail to be called from C#?

Trying to use a powershell script with a function as follows:
function MoveCompressFiles{
Param
(
[Parameter(Mandatory=$true )]
[string] $Des,
[Parameter(Mandatory=$true)]
[string] $Src
)
Add-Type -AssemblyName System.Drawing
$files = Get-ChildItem $Src
foreach ($f in $files) {
if (($f.Length / 1KB) -lt [int32]200) {
Copy-Item -Path $f.FullName -Destination $Des
}
else {
Copy-Item -Path $f.FullName -Destination $Des
while (((Get-Item (($Des).ToString() + "\$f")).Length / 1KB ) -gt 500) {
$img = [System.Drawing.Image]::FromFile((($Des).ToString() + "$f"))
[int32]$new_width = $img.Width * (20 / 100);
[int32]$new_height = $img.Height * (20 / 100);
$img2 = New-Object System.Drawing.Bitmap($new_width, $new_height)
$graph = [System.Drawing.Graphics]::FromImage($img2)
$graph.DrawImage($img, 0, 0, $new_width, $new_height)
$newImgName = "M".ToString() + $f.ToString()
$img2.Save(($Des).ToString()+"\$newImgName")
$img.Dispose()
$img2.Dispose()
Remove-Item ($Des.ToString()+$f)
Rename-Item -Path ($Des.ToString()+$newImgName) -NewName "$f"
Write-Host ((Get-Item ($Des.ToString()+$f)).Length / 1KB )
}
$filesize = $f.Length * 0.8
$filesize=($filesize / 1KB)
#$filesize = [math]::round(($filesize / 1KB), 0)
$abc = "KB"
$filesizeSTR = $filesize.ToString() + $abc
Push-Location $Src
mogrify -path $Des -define jpeg:extent=$filesizeSTR $f
Pop-Location
Write-Host "Moved file $f"
}
}
}
Works in Powershell, however when i try to do it it in my solution,
private static void Powershell()
{
string SCRIPT_PATH = System.IO.File.ReadAllText(#"C:\Untitled2.ps1");
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddScript(SCRIPT_PATH);
ps.Invoke();
ps.AddCommand("MoveCompressFiles").AddParameters(new Dictionary<string, string>
{
{"Des" , #"C:\Des"},
{"Src", #"C:\Src"}
});
}
}
It doesn't work, I've tried some other methods of calling a function from a ps script but it still fails to even move the files to another location
Since you need to dot-source your script file (. <script>) in order to make the MoveCompressFiles function available, which requires an .AddScript() call,
I suggest constructing a single piece of PowerShell code in a string variable that both dot-sources the script and invokes your function via a single .AddScript() call.
However, in order to guarantee that .AddScript() works, you must first ensure that the PowerShell execution policy allows script invocation, using a call to Set-ExecutionPolicy; the code below uses -Scope Process, so as to limit the change to the current process.
Update: There's a simpler way to configure the execution policy, via the initial session state - see this answer.
var SCRIPT_PATH = #"C:\Untitled2.ps1";
var src = #"C:\Src";
var des = #"C:\Des";
var script = $#". ""{SCRIPT_PATH}""; MoveCompressFiles -Des ""{des}"" -Src ""{src}""";
using (PowerShell ps = PowerShell.Create())
{
// Make sure that script execution is allowed.
ps.AddCommand("Set-ExecutionPolicy")
.AddParameter("Scope", "Process")
.AddParameter("ExecutionPolicy", "Bypass")
.AddParameter("Force", true);
ps.Invoke();
// Add the PowerShell code constructed above and invoke it.
ps.AddScript(script);
// Use foreach (var o in ps.Invoke()) { Console.WriteLine(o); } to print the output.
ps.Invoke();
}
Note the simplified, implicit runspace creation, by using PowerShell.Create() only.
The embedded PowerShell code dot-sources your script file (. <script>) in order to define the MoveCompressFiles function, and then invokes the function.
Note that the above, as your own code, doesn't capture or print the output from the PowerShell code (.Invoke()'s output).
To see if errors occurred, you can check ps.HadErrors and examine ps.Streams.Error or any of the other streams, such as .ps.Streams.Information for the Write-Host output (the success stream's output is what .Invoke() returns directly).
For instance, use something like the following to print all errors (messages only) that occurred to the console's standard error stream:
foreach (var o in ps.Streams.Error) {
Console.Error.WriteLine(o);
}
As for what you tried:
ps.AddScript(SCRIPT_PATH); ps.Invoke();
While this executes your script, it does so in a child scope, so the embedded function MoveCompressFiles definition is not added to your session's top-level scope, so the subsequent .AddCommand() call fails, because the MoveCompressFiles function isn't available.
Instead, you must dot-source your script (. <script>), which makes it run in the caller's scope and therefore makes its function definition available there.
As an aside: Despite the .AddScript() method's name, its primary purpose is to execute a piece of PowerShell code, not a script file.
To execute the latter (without dot-sourcing), use .AddCommand().

Cannot Associate Test Case in Visual Studio

I have a standard MSTest unit test in a unit test C# project file. The project is running .NET Framework 4.7.2, and has version 1.3.2 of the MSTest adapter and framework installed. I am running Visual Studio 2017 Enterprise 15.7.6, and have a VSTS workspace with some random manually-created test cases in it.
When I right-click on my unit test in the Test Explorer, and select "Associate to Test Case", I am able to enter the test case ID, add the association, and click "Save". Upon save, I get an error message, below.
I have tried to save the association using different versions of MSTest, and different .NET Framework versions for the project file, neither of which solved the issue. I also tried running Visual Studio as an administrator, which did not work. Has anyone else had this issue, or know of any workarounds?
I test it in my side using two VS2017 versions, they all works well.
For example, I add a simple test case manually in one test plan in VSTS, and then I create a simple unit test project with .net 4.7.2 in my side using VS2017 15.7.6, I could a associate to Test Case in my side.
If possible, you could test it in your side with the following steps:
(1) Test it with other VS machines(The same VS version but not in the same machine if you have).
(2) Clean the VSTS cache. Clean and rebuild your test project in solution explorer window, test it again.
(3) Tools->Options->Work Items, select "Visual Studio(Compatibility mode)" there.
(4) If still no help, add a new test simple unit test project in your VS, uremove the nuget packages: MSTest.TestAdapter and MSTest.TestFramework, and then add a local reference to Microsoft.VisualStudio.QualityTools.UnitTestFramework, view the result again.
Update:
I update my VS2017 to the 15.8.1 version, I got the same issue, that option was disable in default. It would be a real feedback.
https://developercommunity.visualstudio.com/content/problem/309413/cannot-associate-test-case-in-visual-studio.html?childToView=311392#comment-311392
Other members who get the same issue could vote it.
A work around I put together and now use exclusively instead of manual association is to supply the test case id in the test method name and automatically update the case in TFS via the rest API by adding the following PowerShell script to run on a successful build in TFS.
A GUID for each test is generated using the full namespace for the test method and needs to be added to "/fields/Microsoft.VSTS.TCM.AutomatedTestId"
This would need adjusted to your own TFS authentication methods and possibly TFS version (I'm using 2017.2) along with the type of tests you need to read in. This is supporting Coded UI and xUnit. The LoadFrom at the top can be removed if you are not using Coded UI at all.
param (
[string]$Dll = $(throw "-path to test Dll is required.")
)
Write-Warning "$Dll will be locked until this powershell session closes"
#Load for CodedUi Support
[Reflection.Assembly]::LoadFrom(("C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.TestTools.UITesting.dll"))
[Reflection.Assembly]::LoadFrom(("C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll"))
try {
$tests = ([Reflection.Assembly]::LoadFrom(($Dll)).GetTypes().GetMethods() | Where-Object { $_.GetCustomAttributes($false) | Where-Object {$_.TypeId.Name -icontains 'TestMethodAttribute' -or $_.TypeId.Name -icontains 'FactAttribute' -or $_.TypeId.Name -icontains 'SkippableFactAttribute' -or $_.TypeId.Name -icontains 'TheoryAttribute'}} | ForEach-Object { #{ Class = $_.DeclaringType.Name; Name = $_.Name; FullName = $_.DeclaringType.FullName + "." + $_.Name; }})
}
catch {
Write-Error "Could not load or read $dll" -ErrorAction Stop
}
foreach ($test in $tests)
{
$sha1 = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider;
$nameHash = $sha1.ComputeHash([System.Text.Encoding]::Unicode.GetBytes($test.FullName));
[byte[]]$toGuid = [System.Byte[]]::CreateInstance([System.Byte],16);
[System.Array]::Copy($nameHash, $toGuid, 16);
$guid = [guid]::new($toGuid);
$id = ([Regex]::Match($test.Name, "(\d+)(?!.*\d)").Value)
try {
if ($psversiontable.PSVersion.Major -lt 6) {
$currentGUID = (Invoke-RestMethod "http://{instance}[/{team-project}]/_apis/wit/workitems/$($id)?api-version=3.0-preview" -Method Get -UseBasicParsing -UseDefaultCredentials).Fields.'Microsoft.VSTS.TCM.AutomatedTestId'
}
else {
$currentGUID = (Invoke-RestMethod "http://{instance}[/{team-project}]/_apis/wit/workitems/$($id)?api-version=3.0-preview" -Method Get -UseBasicParsing -UseDefaultCredentials -AllowUnencryptedAuthentication).Fields.'Microsoft.VSTS.TCM.AutomatedTestId'
}
}
catch {
$currentGUID = $null;
}
if($currentGUID -ne $guid)
{
Write-Host "Updating $id."
[array]$hash = #{
op = "add";
path = "/fields/Microsoft.VSTS.TCM.AutomatedTestName";
from = $null;
value = $test.FullName;
},#{
op = "add";
path = "/fields/Microsoft.VSTS.TCM.AutomatedTestStorage";
from = $null;
value = (Split-Path $DLL -leaf);
},#{
op = "add";
path = "/fields/Microsoft.VSTS.TCM.AutomatedTestId";
from = $null;
value = $guid;
},#{
op = "add";
path = "/fields/Microsoft.VSTS.TCM.AutomationStatus";
from = $null;
value = "Automated";
},#{
op = "add";
path = "/fields/System.Reason";
from = $null;
value = "Completed";
},#{
op = "add";
path = "/fields/System.State";
from = $null;
value = "Ready";
}
$patch = Convertto-json $hash -Compress
write-host $test.Name
write-host "http://{instance}[/{team-project}]/_apis/wit/workitems/$($id)?api-version=3.0-preview"
if ($psversiontable.PSVersion.Major -lt 6) {
$result = Invoke-RestMethod "http://{instance}[/{team-project}]/_apis/wit/workitems/$($id)?api-version=3.0-preview" -Method Patch -UseBasicParsing -UseDefaultCredentials -Body $patch -ContentType "application/json-patch+json"
}
else {
$result = Invoke-RestMethod "http://{instance}[/{team-project}]/_apis/wit/workitems/$($id)?api-version=3.0-preview" -Method Patch -UseBasicParsing -UseDefaultCredentials -Body $patch -ContentType "application/json-patch+json" -AllowUnencryptedAuthentication
}
}
else {
Write-Host "No changes to $id."
}
}

Why is it not possible to invoke Powershell as a regular process in C#

Wanted to know if there is a reason that Powershell needs a special library (in System.Management.Automation NS) to be invoked from C# code? I have this code:
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
string trackerPath = "C:\\Users\\bernam\\Downloads\\test_scripts\\test_scripts\\Mindaugas_Scripts\\test.ps1";
p.StartInfo.FileName = "Powershell.exe";
p.StartInfo.Arguments = " -file " + trackerPath;
Console.WriteLine(p.StartInfo.FileName + p.StartInfo.Arguments);
p.Start();
string output = p.StandardOutput.ReadToEnd();
And it does not seem to be working - output is not returned back. However all these scenarios work fine (produce desired output):
p.StartInfo.FileName = "Powershell.exe"; p.StartInfo.Arguments = "get-service"; - works fine.
Invoking the PS script from the command line works fine as well (invocation from CMD):
>Powershell.exe -file "C:\Users\<shortened>\test.ps1"
1
2
The powershell code inside the script:
1..10 | % { Write-Host $_ ; sleep -m 500}
I know that there is a recommendation to use PowerShell class in System.Management.Automation namespace. What is interesting to me - is why? And is it possible to use PS without that Class? Maybe my code is simply wrong?
Yes this is certainly possible.
Try replacing Write-Host with Write-Output in the script you are calling.
Write-Host does not write to the standard streams, it writes to a console host. If you are not running a console host (cmd/powershell console), the output will just disappear. In general it is best to avoid using Write-Host all together.
The reason most people recommend using the System.Management.Automation method, is it simplifies many interactions that you may wish to use with powershell, rather than trying to parse the returns from powershell.exe, there are however valid reasons for calling the exe directly, for example if you are using .net core, which doesn't currently fully support System.Management.Automation.
You should invoke PS scripts from System.Management.Automation NS, because then you can work with results and exceptions having type-safe environment.
EDIT also, you can use asynchronous execution or you can execute PS scripts on a Remote server. Generally, you have much more possibilities using that library.
You can take a look at my example below.
string script = ""; // PS script content
List<ScriptParameter> ExecParamList; // parameters
var result = new ExecPSResult(); // Class with list of outputs and errors
using (var powerShellInstance = PowerShell.Create())
{
powerShellInstance.AddScript(script);
foreach (var execParamModel in ExecParamList)
{
powerShellInstance.AddParameter(execParamModel.ParamName,
execParamModel.ParamValue ?? "$null");
}
var psOutput = powerShellInstance.Invoke();
result.Errors =
powerShellInstance.Streams.Error.Select(e =>
ExecException.MakeFromException(e.Exception) // just make models for exeptions
).ToList();
result.OutputItems =
psOutput.Where(
outputItem =>
outputItem != null &&
outputItem.TypeNames[0] != "System.ServiceProcess.ServiceController")
.Select(e => new ExecOutput
{
ObjectTypeFullName = e.BaseObject.GetType().FullName,
ObjectValue = e.BaseObject //This is typeof(Object)
}).ToList();
}
return result;

Using powershell with .NET returning null

I am using .NET with powershell trying to retrieve result of Get-Acl command of specific AD object. Unfortunately when I run the code from C# code I get 0 result. Also the ThrowIfError is not throwing any error.
Command test01 = new Command("import-module");
test01.Parameters.Add("name", "activedirectory");
session.Commands.AddCommand(test01);
Command test0 = new Command("Set-Location");
test0.Parameters.Add("Path", "AD:");
session.Commands.AddCommand(test0);
Command test1 = new Command("Get-Acl");
test1.Parameters.Add("Path", identity);
session.Commands.AddCommand(test1);
session.AddCommand("select-object");
session.AddParameter("Property", "Access");
var tempResults1 = session.Invoke();
ThrowIfError();
private void ThrowIfError()
{
var errors = session.Streams.Error;
if (errors.Count > 0)
{
var ex = errors[0].Exception;
session.Streams.ClearStreams();
// Never close session to dispose already running scripts.
throw ex;
}
}
This code running on server in powershell is working correctly:
PS AD:\> Import-Module -Name activedirectory
PS AD:\> set-location ad:
PS AD:\> get-acl -path <distinguishedNameOfADObject>
Question
How to get the same result like I get from Powershell? I should get atleast something not a zero result.
Little background:
I am trying to get Send-As rights not using Get-ADPermission cmdlet because its taking too long time when I need to search for rights within thousands of mailboxes. Using this article link I am trying another approach to get the rights. I have already the slower version working using C# code:
Command command = new Command("Get-ADPermission");
command.Parameters.Add("Identity", identity);
session.Commands.AddCommand(command);
session.AddCommand("where-object");
ScriptBlock filter = ScriptBlock.Create("$_.ExtendedRights -eq 'send-as'");
session.AddParameter("FilterScript", filter);
session.AddCommand("select-object");
session.AddParameter("Property", "User");
tempResults = session.Invoke();
The better way is to define a powershell-script instead of multiple commands to get the values you need. Example with your powershell-code:
using System.Collections.ObjectModel;
using System.DirectoryServices;
using System.Management.Automation;
namespace GetAclPowershellTest
{
class Program
{
static void Main(string[] args)
{
/****Create Powershell-Environment****/
PowerShell PSI = PowerShell.Create();
/****Insert PowershellScript****/
string Content = "param($object); Import-Module ActiveDirectory; Set-Location AD:; Get-ACL -Path $object"; //Add Scrip
PSI.AddScript(Content);
PSI.AddParameter("object", "<distinguishedNameOfADObject>");
/****Run your Script with PSI.Invoke()***/
Collection<PSObject> PSIResults = PSI.Invoke();
/****All Errors****/
Collection<ErrorRecord> Errors = PSI.Streams.Error.ReadAll();
/****needed, because garbagecollector ignores PSI otherwise****/
PSI.Dispose();
/**** Your ACL-Object ****/
ActiveDirectorySecurity MyACL = (ActiveDirectorySecurity)PSIResults[0].BaseObject;
/*insert your code here*/
}
}
}
This example works for me.
You have to set a reference to the Powershell-Assembly (Usually you can find it at "C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll")
Benefit of this solution is, you could read a .ps1-File you got from someone, fill the parameters with the objects you have and the script runs like in a standard powershell-session. The only requirement to set parameters is the param-part in the Script.
More Infos about param: https://technet.microsoft.com/en-us/library/jj554301.aspx
Hope, this helps...
Greetings, Ronny
Update:
string Content = "param($object); Import-Module ActiveDirectory; Set-Location AD:; (Get-ACL -Path $object).Access | Where-Object{($_.ActiveDirectoryRights -eq 'ExtendedRight') -and ($_.objectType -eq 'ab721a54-1e2f-11d0-9819-00aa0040529b')}";
And the loop at the end looks like this now:
foreach (PSObject o in PSIResults)
{
ActiveDirectoryAccessRule AccessRule = (ActiveDirectoryAccessRule)o.BaseObject;
/**do something with the AccessRule here**/
}

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.

Categories