I am trying to create a Windows Application that will be able to run a variety of Powershell scripts.
I have a script which works as it should (when run from the Powershell prompt), and my Windows Application seems to execute it like it should, but it is unable to find the methods on my OU.
When I execute the script from the Windows Application, I get these messages out:
ERROR: The following exception occurred while retrieving member "Create": "There
is no such object on the server.
"
ERROR: The following exception occurred while retrieving member "Delete": "There
is no such object on the server."
Powershell script:
function New-AdUser {
param (
[string] $Username = $(throw "Parameter -Username [System.String] is required."),
[string] $Password = $(throw "Parameter -Password [System.String] is required."),
[string] $OrganizationalUnit = "Users",
[string] $DisplayName,
[string] $FirstName,
[string] $LastName,
[string] $Initials,
[string] $MobilePhone,
[string] $Description,
[switch] $CannotChangePassword,
[switch] $PasswordNeverExpires,
[switch] $Disabled
)
try {
$currentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dn = $currentDomain.GetDirectoryEntry().distinguishedName
$ou = [ADSI] "LDAP://CN=$OrganizationalUnit,$dn"
$userAccount = $ou.Create("user", "cn=$Username")
$userAccount.SetInfo()
$userAccount.userAccountControl = ($userAccount.userAccountControl.Item(0) -bxor 0x0002) #Enable the account
$userAccount.SetInfo()
$userAccount.sAMAccountName = $Username
$userAccount.SetInfo()
$userAccount.userPrincipalName = ("{0}#{1}" -f $Username, $currentDomain.Name)
if ($DisplayName) {
$userAccount.displayName = $DisplayName
}
if ($Description) {
$userAccount.description = $Description
}
if ($FirstName) {
$userAccount.givenName = $FirstName
}
if ($LastName) {
$userAccount.SN = $LastName
}
if ($Initials) {
$userAccount.initials = $Initials
}
if ($MobilePhone) {
$userAccount.mobile = $MobilePhone
}
$userAccount.SetInfo()
$userAccount.SetPassword($Password)
# Password
if ($PasswordNeverExpires) {
$userAccount.userAccountControl = ($userAccount.userAccountControl.Item(0) -bxor 0x10000)
}
if ($CannotChangePassword) {
$everyOne = [System.Security.Principal.SecurityIdentifier]'S-1-1-0'
$EveryoneDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($Everyone,'ExtendedRight','Deny', [System.Guid]'ab721a53-1e2f-11d0-9819-00aa0040529b')
$self = [System.Security.Principal.SecurityIdentifier]'S-1-5-10'
$SelfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($self,'ExtendedRight','Deny', [System.Guid]'ab721a53-1e2f-11d0-9819-00aa0040529b')
$userAccount.get_ObjectSecurity().AddAccessRule($selfDeny)
$userAccount.get_ObjectSecurity().AddAccessRule($EveryoneDeny)
$userAccount.CommitChanges()
}
$userAccount.SetInfo()
if ($Disabled) {
$userAccount.userAccountControl = ($userAccount.userAccountControl.Item(0) -bxor 0x0002)
}
$userAccount.SetInfo()
} catch {
Write-Error $_
$ou.Delete("user", "cn=$Username")
return $false
}
return $true
}
The C# code I have is this:
PowerShell ps = PowerShell.Create();
ps.AddScript(GetScript("New-AdUser.ps1"));
ps.Invoke();
ps.AddCommand("New-AdUser").AddParameters(
new List<CommandParameter>() {
new CommandParameter("Username", username),
new CommandParameter("Password", password),
new CommandParameter("FirstName", firstName),
new CommandParameter("LastName", lastName),
new CommandParameter("DisplayName", realName),
new CommandParameter("Initials", initials),
new CommandParameter("MobilePhone", mobilePhone),
new CommandParameter("OrganizationalUnit", "Users"),
new CommandParameter("PasswordNeverExpires")
}
);
var results = ps.Invoke();
foreach (var obj in results)
Console.WriteLine(obj.ToString());
if (ps.Streams.Error.Count > 0)
{
foreach (var err in ps.Streams.Error)
Console.WriteLine("ERROR: {0}", err.ToString());
}
Seems that you are just creating a user in AD. By having the c# code calling a powershell script, you are adding another moving part in your script. Why not call it directly in C# code. Check this MSDN article.
The problem appears to be that the Create method on your ADSI object, $ou, doesn't exist. I would check that it is getting created properly. Run the script outside your application to ensure that it works, or have an extra line that displays its members:
$ou | Get-Member
It almost appears as though the Runspace in the application is being created with a restrictive RunspaceConfiguration, so it can't find System.DirectoryServices for the AD functionality you need.
What do you get when you run the following within in your application?
string script = #"[AppDomain]::CurrentDomain.GetAssemblies()";
PowerShell ps = new PowerShell();
ps.AddScript(script);
var output = ps.Invoke();
foreach (var a in output.Select(pso => (System.Reflection.Assembly)pso.BaseObject))
Console.WriteLine("Assembly: " + a.FullName);
When I run that under the debugger in a plain console application I get 28 assemblies (19 outside the debugger), including System.DirectoryServices. The [AppDomain]::CurrentDomain.GetAssemblies() bit shows 16 when I run it on a vanilla command prompt. System.DirectoryServices shows up in all three lists.
When run from within C# I found that I need to add the PowerShell snap-in "Microsoft.Windows.AD" before being able to run the cmdlet's it provides.
Related
We have a need to remotely start/stop IIS websites and app pools, so we can remotely deploy a website.
I have a Websocket app that starts a PowerShell script to complete these activities. I built Powershell scripts to complete that tasks and they work perfectly in the Powershell prompt. However, when I try to run these scripts from the websocket, the scripts run (I have Write-Outputs in the scripts), but nothing happens the site and pool do not change. I don't see anything that says it failed, either. I would appreciate any help that can be given.
Below is an excerpt from the code:
using (PowerShell ps = PowerShell.Create())
{
string scriptContent = string.Empty;
string pathToReadScriptFile = string.Empty;
// add a script that creates a new instance of an object from the caller's namespace
if (r.StartStop.ToLower() == "stop")
{
pathToReadScriptFile = Path.Combine(scriptsPath, "StopPoolAndSite.ps1");
}
else
{
pathToReadScriptFile = Path.Combine(scriptsPath, "StartPoolAndSite.ps1");
}
using (StreamReader sr = new StreamReader(pathToReadScriptFile))
{
scriptContent = sr.ReadToEnd();
sr.Close();
}
ps.AddScript(scriptContent);
ps.AddParameter("siteName", r.SiteName);
ps.AddParameter("poolName", r.PoolName);
// invoke execution on the pipeline (collecting output)
Collection<PSObject> PSOutput = ps.Invoke();
// loop through each output object item
foreach (PSObject outputItem in PSOutput)
{
if (outputItem != null)
{
await SendMessageToAllAsync($"{outputItem.ToString()}");
}
}
}
Here is one of the powershell script code:
Param(
[Parameter(Mandatory=$True)]
[string]$siteName ,
[string]$poolName
)
if (-Not $poolName)
{
$poolName = $siteName
Write-Output "PoolName not supplied. Using $siteName as default. "
}
Import-Module WebAdministration
Write-Output "Preparing to Start AppPool: $poolName"
Write-Output "(OutPut)Preparing to Start AppPool: $poolName"
Start-WebAppPool $poolName
Write-Output "Preparing to Start Site: $siteName"
Start-WebSite $siteName
Get-WebSite $siteName
Actually i would suggest not to reinvent the wheel there is a project for that check these out :
https://github.com/microsoft/iis.administration
https://manage.iis.net/get
This script works when running in PowerShell ISE (it sets the given user's Remote Desktop Services Profile settings in Active Directory):
Get-ADUser FirstName.LastName | ForEach-Object {
$User = [ADSI]"LDAP://$($_.DistinguishedName)"
$User.psbase.invokeset("TerminalServicesProfilePath","\\Server\Share\HomeDir\Profile")
$User.psbase.invokeset("TerminalServicesHomeDrive","H:")
$User.psbase.invokeset("TerminalServicesHomeDirectory","\\Server\Share\HomeDir")
$User.setinfo()
}
But when I try running it from a C# application I get an error for each invokeset that I call:
Exception calling "InvokeSet" with "2" argument(s):
"Unknown name. (Exception from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))"
Here is the code, which is inside my PowerShell class:
public static List<PSObject> Execute(string args)
{
var returnList = new List<PSObject>();
using (var powerShellInstance = PowerShell.Create())
{
powerShellInstance.AddScript(args);
var psOutput = powerShellInstance.Invoke();
if (powerShellInstance.Streams.Error.Count > 0)
{
foreach (var error in powerShellInstance.Streams.Error)
{
Console.WriteLine(error);
}
}
foreach (var outputItem in psOutput)
{
if (outputItem != null)
{
returnList.Add(outputItem);
}
}
}
return returnList;
}
And I call it like this:
var script = $#"
Get-ADUser {newStarter.DotName} | ForEach-Object {{
$User = [ADSI]""LDAP://$($_.DistinguishedName)""
$User.psbase.invokeset(""TerminalServicesProfilePath"",""\\file\tsprofiles$\{newStarter.DotName}"")
$User.psbase.invokeset(""TerminalServicesHomeDrive"",""H:"")
$User.psbase.invokeset(""TerminalServicesHomeDirectory"",""\\file\home$\{newStarter.DotName}"")
$User.setinfo()
}}";
PowerShell.Execute(script);
Where newStarter.DotName contains the (already existing) AD user's account name.
I tried including Import-Module ActveDirectory at the top of the C# script, but with no effect. I also called $PSVersionTable.PSVersion in both the script running normally and the C# script and both return that version 3 is being used.
After updating the property names to
msTSProfilePath
msTSHomeDrive
msTSHomeDirectory
msTSAllowLogon
I am getting this error in C#:
Exception calling "setinfo" with "0" argument(s): "The attribute syntax specified to the directory service is invalid.
And querying those properties in PowerShell nothing (no error but also no output)
Does anyone happen to know what could cause this?
Many thanks
Updated answer: It seems that these attributes don't exist in 2008+. Try these ones instead:
msTSAllowLogon
msTSHomeDirectory
msTSHomeDrive
msTSProfilePath
See the answer in this thread for the full explanation.
Original Answer:
The comment from Abhijith pk is probably the answer. You need to run Import-Module ActiveDirectory, just like you need to do in the command line PowerShell.
If you've ever run Import-Module ActiveDirectory in the PowerShell command line, you'll know it takes a while to load. It will be the same when run in C#. So if you will be running several AD commands in your application, you would be better off keeping a Runspace object alive as a static object and reuse it, which means you only load the ActiveDirectory module once.
There is details here about how to do that in C#:
https://blogs.msdn.microsoft.com/syamp/2011/02/24/how-to-run-an-active-directory-ad-cmdlet-from-net-c/
Particularly, this is the code:
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { "activedirectory" });
Runspace myRunSpace = RunspaceFactory.CreateRunspace(iss);
myRunSpace.Open();
I'm running an app in PowerShell like so:
$exe = "C:\blah\build\blah\Release\blahblah.exe"
&$exe scheduledRun sliceBicUp useEditionId
blahblah.exe is a C# .NET 4.5 Console App. Now I know this executable can throw errors etc. Can I catch these errors/exceptions within the PowerShell script itself?
Basically i want the PowerShell script to detect an error/exception has occurred and action something, like email our Helpdesk for example.
As #Liam mentioned, errors from external programs are not exceptions. If the executable terminates with a proper exit code you could check the automatic variable $LastExitCode and react to its value:
& $exe scheduledRun sliceBicUp useEditionId
switch ($LastExitCode) {
0 { 'success' }
1 { 'error A' }
2 { 'error B' }
default { 'catchall' }
}
The only other thing you could do is parse the output for error messages:
$output = &$exe scheduledRun sliceBicUp useEditionId *>&1
if ($output -like '*some error message*') {
'error XY occurred'
}
you can use this code. When .Net program exit then error is passed to ps script
$exe = "C:\Users\johnn\OneDrive\Documents\visual studio 2015\Projects\test\test\bin\Release\test.exe"
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $exe
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
I'm developing a Console application that must read from a PS1 file and execute the command from a Form.
My runs very well when I have to call simple function PS1
I have this scrip1 that :
=======================================================================
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
#Function to copy user permissions
Function Copy-UserPermissions($SourceUserID, $TargetUserID, [Microsoft.SharePoint.SPSecurableObject]$Object)
{
#Determine the given Object type and Get URL of it
Switch($Object.GetType().FullName)
{
"Microsoft.SharePoint.SPWeb" { $ObjectType = "Site" ; $ObjectURL = $Object.URL; $web = $Object }
"Microsoft.SharePoint.SPListItem"
{
if($Object.Folder -ne $null)
{
$ObjectType = "Folder" ; $ObjectURL = "$($Object.Web.Url)/$($Object.Url)"; $web = $Object.Web
}
else
{
$ObjectType = "List Item"; $ObjectURL = "$($Object.Web.Url)/$($Object.Url)" ; $web = $Object.Web
}
}
#Microsoft.SharePoint.SPList, Microsoft.SharePoint.SPDocumentLibrary, Microsoft.SharePoint.SPPictureLibrary,etc
default { $ObjectType = "List/Library"; $ObjectURL = "$($Object.ParentWeb.Url)/$($Object.RootFolder.URL)"; $web = $Object.ParentWeb }
}
#Get Source and Target Users
$SourceUser = $Web.EnsureUser($SourceUserID)
$TargetUser = $Web.EnsureUser($TargetUserID)
#Get Permissions of the Source user on given object - Such as: Web, List, Folder, ListItem
$SourcePermissions = $Object.GetUserEffectivePermissionInfo($SourceUser)
#Iterate through each permission and get the details
foreach($SourceRoleAssignment in $SourcePermissions.RoleAssignments)
{
#Get all permission levels assigned to User account directly or via SharePOint Group
$SourceUserPermissions=#()
foreach ($SourceRoleDefinition in $SourceRoleAssignment.RoleDefinitionBindings)
{
#Exclude "Limited Accesses"
if($SourceRoleDefinition.Name -ne "Limited Access")
{
$SourceUserPermissions += $SourceRoleDefinition.Name
}
}
#Check Source Permissions granted directly or through SharePoint Group
if($SourceUserPermissions)
{
if($SourceRoleAssignment.Member -is [Microsoft.SharePoint.SPGroup])
{
$SourcePermissionType = "'Member of SharePoint Group - " + $SourceRoleAssignment.Member.Name +"'"
#Add Target User to the Source User's Group
#Get the Group
$Group = [Microsoft.SharePoint.SPGroup]$SourceRoleAssignment.Member
#Check if user is already member of the group - If not, Add to group
if( ($Group.Users | where {$_.UserLogin -eq $TargetUserID}) -eq $null )
{
#Add User to Group
$Group.AddUser($TargetUser)
#Write-Host Added to Group: $Group.Name
}
}
else
{
$SourcePermissionType = "Direct Permission"
#Add Each Direct permission (such as "Full Control", "Contribute") to Target User
foreach($NewRoleDefinition in $SourceUserPermissions)
{
#Role assignment is a linkage between User object and Role Definition
$NewRoleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($TargetUser)
$NewRoleAssignment.RoleDefinitionBindings.Add($web.RoleDefinitions[$NewRoleDefinition])
$object.RoleAssignments.Add($NewRoleAssignment)
$object.Update()
}
}
$SourceUserPermissions = $SourceUserPermissions -join ";"
Write-Host "***$($ObjectType) Permissions Copied: $($SourceUserPermissions) at $($ObjectURL) via $($SourcePermissionType)***"
}
}
}
Function Clone-SPUser($SourceUserID, $TargetUserID, $WebAppURL)
{
###Check Whether the Source Users is a Farm Administrator ###
Write-host "Scanning Farm Administrators Group..."
#Get the SharePoint Central Administration site
$AdminWebApp = Get-SPwebapplication -includecentraladministration | where {$_.IsAdministrationWebApplication}
$AdminSite = Get-SPWeb $AdminWebApp.Url
$AdminGroupName = $AdminSite.AssociatedOwnerGroup
$FarmAdminGroup = $AdminSite.SiteGroups[$AdminGroupName]
#enumerate in farm adminidtrators groups
foreach ($user in $FarmAdminGroup.users)
{
if($User.LoginName.Endswith($SourceUserID,1)) #1 to Ignore Case
{
#Add the target user to Farm Administrator Group
$FarmAdminGroup.AddUser($TargetUserID,"",$TargetUserID , "")
Write-Host "***Added to Farm Administrators Group!***"
}
}
### Check Web Application User Policies ###
Write-host "Scanning Web Application Policies..."
$WebApp = Get-SPWebApplication $WebAppURL
foreach ($Policy in $WebApp.Policies)
{
#Check if the search users is member of the group
if($Policy.UserName.EndsWith($SourceUserID,1))
{
#Write-Host $Policy.UserName
$PolicyRoles=#()
foreach($Role in $Policy.PolicyRoleBindings)
{
$PolicyRoles+= $Role
}
}
}
#Add Each Policy found
if($PolicyRoles)
{
$WebAppPolicy = $WebApp.Policies.Add($TargetUserID, $TargetUserID)
foreach($Policy in $PolicyRoles)
{
$WebAppPolicy.PolicyRoleBindings.Add($Policy)
}
$WebApp.Update()
Write-host "***Added to Web application Policies!***"
}
### Drill down to Site Collections, Webs, Lists & Libraries, Folders and List items ###
#Get all Site collections of given web app
$SiteCollections = Get-SPSite -WebApplication $WebAppURL -Limit All
#Convert UserID Into Claims format - If WebApp is claims based! Domain\User to i:0#.w|Domain\User
if( (Get-SPWebApplication $WebAppURL).UseClaimsAuthentication)
{
$SourceUserID = (New-SPClaimsPrincipal -identity $SourceUserID -identitytype 1).ToEncodedString()
$TargetUserID = (New-SPClaimsPrincipal -identity $TargetUserID -identitytype 1).ToEncodedString()
}
#Loop through all site collections
foreach($Site in $SiteCollections)
{
#Prepare the Target user
$TargetUser = $Site.RootWeb.EnsureUser($TargetUserID)
Write-host "Scanning Site Collection Administrators Group for:" $site.Url
###Check Whether the User is a Site Collection Administrator
foreach($SiteCollAdmin in $Site.RootWeb.SiteAdministrators)
{
if($SiteCollAdmin.LoginName.EndsWith($SourceUserID,1))
{
#Make the user as Site collection Admin
$TargetUser.IsSiteAdmin = $true
$TargetUser.Update()
Write-host "***Added to Site Collection Admin Group***"
}
}
#Get all webs
$WebsCollection = $Site.AllWebs
#Loop throuh each Site (web)
foreach($Web in $WebsCollection)
{
if($Web.HasUniqueRoleAssignments -eq $True)
{
Write-host "Scanning Site:" $Web.Url
#Call the function to Copy Permissions to TargetUser
Copy-UserPermissions $SourceUserID $TargetUserID $Web
}
#Check Lists with Unique Permissions
Write-host "Scanning Lists on $($web.url)..."
foreach($List in $web.Lists)
{
if($List.HasUniqueRoleAssignments -eq $True -and ($List.Hidden -eq $false))
{
#Call the function to Copy Permissions to TargetUser
Copy-UserPermissions $SourceUserID $TargetUserID $List
}
#Check Folders with Unique Permissions
$UniqueFolders = $List.Folders | where { $_.HasUniqueRoleAssignments -eq $True }
#Get Folder permissions
foreach($folder in $UniqueFolders)
{
#Call the function to Copy Permissions to TargetUser
Copy-UserPermissions $SourceUserID $TargetUserID $folder
}
#Check List Items with Unique Permissions
$UniqueItems = $List.Items | where { $_.HasUniqueRoleAssignments -eq $True }
#Get Item level permissions
foreach($item in $UniqueItems)
{
#Call the function to Copy Permissions to TargetUser
Copy-UserPermissions $SourceUserID $TargetUserID $Item
}
}
}
}
Write-Host "Permission are copied successfully!"
}
#Define variables for processing
$WebAppURL = "http://sp2010devid/sites/EsercioWeekend/"
#Provide input for source and Target user Ids
$SourceUser ="virtualsp\admnistrator"
$TargetUser ="virtualsp\b.ferreirarocha"
#Call the function to clone user access rights
Clone-SPUser $SourceUser $TargetUser $WebAppURL
==========================================================================
And the most important part of my C# Code that runs the script:
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace HowToRunPowerShell
{
public partial class FormPowerShellSample : Form
{
// Represents the kind of drag drop formats we want to receive
private const string dragDropFormat = "FileDrop";
public FormPowerShellSample()
{
InitializeComponent();
}
private void buttonRunScript_Click(object sender, EventArgs e)
{
try
{
textBoxOutput.Clear();
textBoxOutput.Text = RunScript(textBoxScript.Text);
}
catch (Exception error)
{
textBoxOutput.Text += String.Format("\r\nError in script : {0}\r\n", error.Message);
}
}
/// <summary>
/// Runs the given powershell script and returns the script output.
/// </summary>
/// <param name="scriptText">the powershell script text to run</param>
/// <returns>output of the script</returns>
private string RunScript(string scriptText)
{
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
PSSnapInException snapInError;
runspace.RunspaceConfiguration.AddPSSnapIn("Microsoft.SharePoint.PowerShell", out snapInError);
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
// add an extra command to transform the script output objects into nicely formatted strings
// remove this line to get the actual objects that the script returns. For example, the script
// "Get-Process" returns a collection of System.Diagnostics.Process instances.
pipeline.Commands.Add("Out-String");
// execute the script
Collection<PSObject> results = pipeline.Invoke();
// close the runspace
runspace.Close();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
#region Drag-drop handling events
private void FormPowerShellSample_DragDrop(object sender, DragEventArgs e)
{
// is it the correct type of data?
if (e.Data.GetDataPresent(dragDropFormat))
{
// dragging files onto the window yields an array of pathnames
string[] files = (string[])e.Data.GetData(dragDropFormat);
if (files.Length > 0)
{
// just read the first file
using (StreamReader sr = new StreamReader(files[0]))
{
// and plunk the contents in the textbox
textBoxScript.Text = sr.ReadToEnd();
}
}
}
}
private void FormPowerShellSample_DragEnter(object sender, DragEventArgs e)
{
// only accept the dropped data if it has the correct format
e.Effect = e.Data.GetDataPresent(dragDropFormat) ? DragDropEffects.Link : DragDropEffects.None;
}
#endregion
}
}
but when I try to run the script it return me the the following exception :
“Cannot Invoke this function because the current host does not
implement it”
I found this post link and it Seems it Someone has found the same isseus and solve it with this procedure but i'm completely ignorant on PowerSehll.
To run arbitrary PowerShell scripts from .net code, you need to implement a host.
Here are my blog posts on the subject: http://powershellstation.com/category/writing-a-host/
Since you have the script in question, you can simply eliminate the parts of it which require a host. You have most of the write-host calls commented out. Try commenting out the rest.
Other things that cause problems are prompting for input and using -confirm.
private static void Main(string[] args)
{
// Display the welcome message.
Console.Title = "PowerShell Console Host Sample Application";
ConsoleColor oldFg = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(" Windows PowerShell Console Host Application Sample");
Console.WriteLine(" ==================================================");
Console.WriteLine(string.Empty);
Console.WriteLine("This is an example of a simple interactive console host uses ");
Console.WriteLine("the Windows PowerShell engine to interpret commands.");
Console.WriteLine("Type 'exit' to exit.");
Console.WriteLine(string.Empty);
Console.ForegroundColor = oldFg;
// Create the listener and run it. This method never returns.
PSListenerConsoleSample listener = new PSListenerConsoleSample();
listener.Run();
}
/// <summary>
/// Initializes a new instance of the PSListenerConsoleSample class.
/// </summary>
public PSListenerConsoleSample()
{
// Create the host and runspace instances for this interpreter.
// Note that this application does not support console files so
// only the default snap-ins will be available.
this.myHost = new MyHost(this);
this.myRunSpace = RunspaceFactory.CreateRunspace(this.myHost);
this.myRunSpace.Open();
// Create a PowerShell object to run the commands used to create
// $profile and load the profiles.
lock (this.instanceLock)
{
this.currentPowerShell = PowerShell.Create();
}
try
{
this.currentPowerShell.AddScript("Add-PSSnapin Microsoft.Sharepoint.Powershell");
this.currentPowerShell.AddScript(#"C:\Users\Administrator\Desktop\Untitled1.ps1");
this.currentPowerShell.Runspace = this.myRunSpace;
PSCommand[] profileCommands = Microsoft.Samples.PowerShell.Host.HostUtilities.GetProfileCommands("SampleHost06");
foreach (PSCommand command in profileCommands)
{
this.currentPowerShell.Commands = command;
this.currentPowerShell.Invoke();
}
}
finally
{
// Dispose the PowerShell object and set currentPowerShell
// to null. It is locked because currentPowerShell may be
// accessed by the ctrl-C handler.
lock (this.instanceLock)
{
this.currentPowerShell.Dispose();
this.currentPowerShell = null;
}
}
}
/// <summary>
POWERSHELL PS1
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
function Get-SPUserEffectivePermissions(
[object[]]$users,
[Microsoft.SharePoint.SPSecurableObject]$InputObject) {
begin { }
process {
$so = $InputObject
if ($so -eq $null) { $so = $_ }
if ($so -isnot [Microsoft.SharePoint.SPSecurableObject]) {
throw "A valid SPWeb, SPList, or SPListItem must be provided."
}
foreach ($user in $users) {
# Set the users login name
$loginName = $user
if ($user -is [Microsoft.SharePoint.SPUser] -or $user -is [PSCustomObject]) {
$loginName = $user.LoginName
}
if ($loginName -eq $null) {
throw "The provided user is null or empty. Specify a valid SPUser object or login name."
}
# Get the users permission details.
$permInfo = $so.GetUserEffectivePermissionInfo($loginName)
# Determine the URL to the securable object being evaluated
$resource = $null
if ($so -is [Microsoft.SharePoint.SPWeb]) {
$resource = $so.Url
} elseif ($so -is [Microsoft.SharePoint.SPList]) {
$resource = $so.ParentWeb.Site.MakeFullUrl($so.RootFolder.ServerRelativeUrl)
} elseif ($so -is [Microsoft.SharePoint.SPListItem]) {
$resource = $so.ParentList.ParentWeb.Site.MakeFullUrl($so.Url)
}
# Get the role assignments and iterate through them
$roleAssignments = $permInfo.RoleAssignments
if ($roleAssignments.Count -gt 0) {
foreach ($roleAssignment in $roleAssignments) {
$member = $roleAssignment.Member
# Build a string array of all the permission level names
$permName = #()
foreach ($definition in $roleAssignment.RoleDefinitionBindings) {
$permName += $definition.Name
}
# Determine how the users permissions were assigned
$assignment = "Direct Assignment"
if ($member -is [Microsoft.SharePoint.SPGroup]) {
$assignment = $member.Name
} else {
if ($member.IsDomainGroup -and ($member.LoginName -ne $loginName)) {
$assignment = $member.LoginName
}
}
# Create a hash table with all the data
$hash = #{
Resource = $resource
"Resource Type" = $so.GetType().Name
User = $loginName
Permission = $permName -join ", "
"Granted By" = $assignment
}
# Convert the hash to an object and output to the pipeline
New-Object PSObject -Property $hash
}
}
}
}
end {}
}
Get-SPSite -Limit All | Get-SPWeb | Get-SPUserEffectivePermissions "virtualsp\administrator" | Export-Csv -NoTypeInformation -Path c:\perms.csv
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.