Executing PowerShell using PHP and IIS

This is an article on how to develop a PHP page to execute a PowerShell script on IIS 7.5 (7.0 is fine) as logged on user. In short, it makes use of shell_exec in PHP to launch PowerShell, grab the output and display it to the browser. It details the server setup as I have not tested this on other configurations, and provides the most basic possible PHP and PowerShell script for you to test it out. Hopefully some find this useful!

 

Introduction:

I’ve been using PowerShell fairly regularly at work for about a year now and it’s fantastic. Many tasks that our team have to perform are now scripted using PowerShell, and we’re actively trying to turn away from VBS. Given the comparative ease of this language and its integration with the .NET framework, we started toying with the idea of being able to provide an IIS website front end to our service desk to execute PowerShell scripts behind the scenes.

Essentially we wanted a website that would allow for an Active Directory user to login over HTTPS, enter some data into a web page, submit the data to a digitally signed PowerShell script as parameters and have it execute on the very same server as the logged on user, finally returning the result back to the web page. Initially the solution needed to execute PowerShell scripts that would make at least make use of the Microsoft Active Directory Module and VMware’s PowerCLI Snap-in, later expanding to utilise the Exchange 2010 and Netapp DataONTAP snap-ins.

After initially looking at a Perl wrapper for executing PowerShell with a colleague and making little progress, I decided to have a shot at doing it with PHP. I have a little experience with PHP and within 30 minutes or so I had it working using shell_exec. Game on!

 

Server Configuration:

The working solution is based on the following platform:

  • Windows Server 2008 R2 with IIS 7.5.
  • PowerShell v2 (the default on the above OS).
  • .NET 4 (not necessary but this is our setup).
  • PHP (x64 preferred, though x86 will work – discussed below).
  • Visual C++ 2008 SP1 Redistributable Package – choose the package to match the version of PHP (x64) (x86).
  • Active Directory Web Services installed on one of our 2008 DCs. This allows for the use of the Active Directory module (cmdlets) on a 2008 R2 or Windows 7 machine in a non 2008 R2 domain controller environment.

I won’t cover securely signing PowerShell scripts here as it’s widely documented. If you wish to do this, it’s useful to remember to run Set-ExecutionPolicy on both versions:

  • C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Powershell.exe
  • C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe

1. Install IIS with the following:

  • CGI
  • Basic Authentication
  • Recommended: URL authorization (if you want to limit access to particular Active Directory users or groups).
  • Optional: IP and Domain Restrictions (if you want to limit access to a particular IP range on your LAN).
  • Install any Windows patches that may be needed by adding IIS to your server.

2. Install PHP:
Note: PHP.net only compile and officially support x86 versions of PHP. Unfortunately this means that the x86 version of PowerShell is launched and may prove a limitation for you; in our case we wanted to load Exchange 2010 snap-ins which require the x64 version of PowerShell, and so needed x64 PHP. Anindya over at http://www.anindya.com/ very kindly compiles these as x64 when PHP releases a new version.

  1. Download and extract the latest “VC9 x64 Non Thread Safe” version of PHP from http://www.anindya.com/. Put this somewhere you want to keep PHP (e.g. F:\PHP).
  2. UPDATE: PHP.net now do x64 builds. Use the latest x64 non thread safe version from here: http://windows.php.net/download/
  3. Install PHP Manager from http://phpmanager.codeplex.com/.
  4. Add the location of PHP (e.g. F:\PHP;) to the Path environment variable.

3. Configure IIS:

  1. Within IIS Manager, right click on “Sites” and choose “Add Web Site…”. When trying to think of an acronym for the test website I came up with WAM (Web Automated Management) and that stuck. Specify your website name, a physical path (e.g. C:\inetpub\wwwroot\wam), hostname and IP address binding.
  2. Select the website and within the Authentication module for the new site, enable Basic and disable the Anonymous authentication. Remember, if your website is not secured using HTTPS, passwords will be sent over the network in plain text. This may not worry you but I need to point it out. There are many guides online on how to create a self signed SSL certificate for your IIS server.
  3. Again within the website, select the PHP Manager module and click “Register new PHP version” and locate your php-cgi.exe executable.

4. Configure PHP:

  1. Open PHP.ini. You will likely have to change some of these depending on your setup. I’ve described what I changed by specifying the initial value which you can search for, then the pipe symbol and the new value (e.g. search for | modify to):
    • error_log = “C:\Windows\Temp\php-5.4.3_errors.log” | error_log = “F:\php-errors\php-5.4.3_errors.log”
    • max_execution_time = 300 | max_execution_time = 600
    • display_errors = Off | display_errors = On
    • date.timezone = “Europe/Minsk” = | date.timezone = Europe/London
  2. Grant the local IIS_IUSRS group modify NTFS permission on the log folder for the PHP error log.

 

Writing the PHP Page:

Now, our PHP pages are a little more complicated than the example I’m going to give here. We only display certain pages to certain groups of users. We validate our submitted data using PHP functions. We return information to the screen if required fields aren’t filled in before submitting the form. We use jQuery mobile to present a mobile version to mobile users (I love this ability). The list goes on. You can make your PHP as advanced (but user friendly!) as you want; that’s the fun in developing websites :) Here I’ll just show how you can write an extremely simple page to pass the data to a PowerShell script. If you want to take things into a production environment I suggest you expand on this as we have done, but this serves as a proof of concept :)

Example PHP Page:

Let’s call this page “get-process.php”, and save it into the root of the created website:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Testing PowerShell</title>
</head>
<body>
<?php

// If there was no submit variable passed to the script (i.e. user has visited the page without clicking submit), display the form:
if(!isset($_POST["submit"]))
{
	?>
	<form name="testForm" id="testForm" action="get-process.php" method="post" />
		Your name: <input type="text" name="username" id="username" maxlength="20" /><br />
		<input type="submit" name="submit" id="submit" value="Do stuff" />
	</form>
	<?php	
}
// Else if submit was pressed, check if all of the required variables have a value:
elseif((isset($_POST["submit"])) && (!empty($_POST["username"])))
{
	// Get the variables submitted by POST in order to pass them to the PowerShell script:
	$username = $_POST["username"];
	// Best practice tip: We run out POST data through a custom regex function to clean any unwanted characters, e.g.:
	// $username = cleanData($_POST["username"]);
		
	// Path to the PowerShell script. Remember double backslashes:
	$psScriptPath = "F:\\get-process.ps1";

	// Execute the PowerShell script, passing the parameters:
	$query = shell_exec("powershell -command $psScriptPath -username '$username'< NUL");
	echo $query;	
}
// Else the user hit submit without all required fields being filled out:
else
{
	echo "Sorry, you did not complete all required fields. Please go back and try again.";
}
?>
</body>
</html>

 

Writing the PowerShell Script:

Call this get-process.ps1. Save it to the location specified in the above PHP script (it can be anywhere you like, but I recommend they are outside the website root. We actually store ours on a different drive). Naming conventions are useful and in this instance it’s handy to use the same name for both PHP and PS1 script for troubleshooting.

#*=============================================================================
#* Script Name: get-process.ps1
#* Created: 	2012-01-01
#* Author: 	Robin Malik
#* Purpose: 	This is a simple script that executes get-process.
#*			
#*=============================================================================

#*=============================================================================
#* PARAMETER DECLARATION
#*=============================================================================
param(
[string]$username
)
#*=============================================================================
#* REVISION HISTORY
#*=============================================================================
#* Date: 
#* Author:
#* Purpose:
#*=============================================================================

#*=============================================================================
#* IMPORT LIBRARIES
#*=============================================================================

#*=============================================================================
#* PARAMETERS
#*=============================================================================

#*=============================================================================
#* INITIALISE VARIABLES
#*=============================================================================
# Increase buffer width/height to avoid PowerShell from wrapping the text before
# sending it back to PHP (this results in weird spaces).
$pshost = Get-Host
$pswindow = $pshost.ui.rawui
$newsize = $pswindow.buffersize
$newsize.height = 3000
$newsize.width = 400
$pswindow.buffersize = $newsize

#*=============================================================================
#* EXCEPTION HANDLER
#*=============================================================================

#*=============================================================================
#* FUNCTION LISTINGS
#*=============================================================================

#*=============================================================================
#* Function: 	function1
#* Created: 	2012-01-01
#* Author: 	My Name
#* Purpose: 	This function does X Y Z
#* =============================================================================

#*=============================================================================
#* END OF FUNCTION LISTINGS
#*=============================================================================

#*=============================================================================
#* SCRIPT BODY
#*=============================================================================
Write-Output "Hello $username <br />"

# Get a list of running processes:
$processes = Get-Process

# Write them out into a table with the columns you desire:
Write-Output "<table>"
Write-Output "<thead>"
Write-Output "	<tr>"
Write-Output "		<th>Process Name</th>"
Write-Output "		<th>Id</th>"
Write-Output "		<th>CPU</th>"
Write-Output "	</tr>"
Write-Output "</thead>"
Write-Output "<tfoot>"
Write-Output "	<tr>"
Write-Output "		<td>&nbsp;</td>"
Write-Output "		<td>&nbsp;</td>"
Write-Output "		<td>&nbsp;</td>"
Write-Output "	</tr>"
Write-Output "</tfoot>"
Write-Output "<tbody>"
foreach($process in $processes)
{
Write-Output "	<tr>"
Write-Output "		<td>$($process.Name)</td>"
Write-Output "		<td>$($process.Id)</td>"
Write-Output "		<td>$($process.CPU)</td>"
Write-Output "	</tr>"
}
Write-Output "</tbody>"
Write-Output "</table>"
#*=============================================================================
#* END SCRIPT BODY
#*=============================================================================

#*=============================================================================
#* END OF SCRIPT
#*=============================================================================

 

Use Cases:

That’s all there is to it! You can visit the webpage, enter your name and submit the form. What you do from here is really up to you and only limited by your talent and imagination :) We’ve created pages that save an incredible amount of work for both us and the service desk and so finally, some examples on how we use them which may (or may not) inspire you.

It’s worth noting here that where applicable, the PowerShell scripts also log this data to our “changelog” website (a bespoke system/website to record changes made to servers/devices) via an API call using System.Net.WebClient. This saves a *lot* of fiddly manual work, remembering to record the changes after you’ve made them(!) and allows for a consistent method of recording changes for specific actions.

Just some of the pages we have in use at the moment allow for:

  • Creation of different kinds of AD accounts to be created (service accounts, admin accounts, remote accounts).
  • Resetting a user’s password (generating a new one).
  • Retrieval of detailed information on an AD user (e.g. name, phone number, email, true last logon, locked out status, enabled status, Exchange email quota, mailbox size, home drive usage etc.).
  • Adding/removing users to/from AD groups or local groups on computers/servers.
  • Retrieval of detailed server information (installed software, local admins/remote desktop users, configured IP addresses, accessible shares etc.).
  • Granting full access and send as permissions on an Exchange mailbox to another user.
  • Increasing a user’s email quota.
  • Resetting the permissions on a user’s home drive (calling setacl.exe).
  • Changing of a user’s display name.

 

Caveats:

  • Obviously the webpage is going to wait while the PowerShell script is executing. I’ve had to increase the max_timeout value in php.ini from 300 to 600 to allow for 10 minutes of PHP execution time before quitting. I encounted this when resetting the permissions on a user’s home drive that was *quite large*. If the PHP timeout value is reached, you’ll receive an error 500. The PowerShell process will continue to run and complete, but it’s nice to avoid this.
  • If you want your displayed output to be nice, you will need to write HTML within PowerShell. In the above PowerShell script you will notice use of HTML so that when PHP displays the result we get a table.
  • If you’re considering using Start-Transcript to log within your PowerShell script, it doesn’t work. Launching PowerShell via shell_exec *within a PHP page* (not from the PHP command line) will result in a transcript file that has a header, footer, but no content.

78 thoughts on “Executing PowerShell using PHP and IIS

  1. Hello Robin,
    I am a sysadmin with 3 years Powershell experience and today started on php, in fact, your Introduction paragraph describes me for 90 %.
    As we’re 4 months later now, can you tell me if your idea of building sites for service desk (and colleagues) along the lines you describe , was it the right idea? Have you already created a lot of useful tools?
    One small remark: your Powershell script will be a lot shorter if you use the ConvertTo-Html cmdlet.

  2. Pingback: theboywonder.co.uk
  3. Hi Jacques,

    Thanks for your comment. While my post was written in July this has actually been live for around 10 months now and I’m happy to say it’s been a great success. We’re slowly offloading more and more trivial (but crucial) tasks to the Service Desk over time. The time savings alone have been worth it – much faster responses for our end users, but in addition a consistent approach every time. Trying to imagine working without it now seems quite painful ;)

    You’re right – it’s possible to use ConvertTo-Html but would depend entirely on your setup. For us it’s not suitable as ConvertTo-Html as this outputs an entire HTML page (i.e. the full HTML structure). As you can see by the basic PHP page we’re retrieving the contents of the PowerShell script into $query and echoing that out into the existing page after shell_exec completes. This would result in echoing an entire HTML page inside the existing PHP/HTML page (I’m not even sure what would happen but I think it could get very messy?).

    In addition we also have a lot of CSS styling that would be hard to replicate (and avoid conflicts) if we used that cmdlet. I like the flexibility that writing my own HTML gives me; for example using the above TABLE structure (rather than anything ConvertTo-Html produces) allows me to assign it a specific CSS class and use the jQuery TableSorter plugin to allow for sortable tables of data (if necessary for the specific task).

    Let me know if you do go ahead and implement a solution!

  4. Thanks Author for sharing these useful tips. We are working on transfer basic server admin works to our service desk. I created similar webpage as you did above. But I have a question that how you to evaluate normal user privilege to admin privilege in script so that the powershelll script can have right to execute administrative work.

  5. Hi Andy,

    You can do this in two ways:

    1) When using Basic authentication (over SSL of course!) you’ll find the that the PowerShell process is launched as the user who authenticated against IIS. This assumes that you have not modified the default application pool identity for the website (default being “ApplicationPoolIdentity”).
    Of course for this to be useful you must also grant the user conducting the work via the webpage, permissions to do that work on the relevant resources (e.g. permissions to create objects in a certain OU, or permission to edit mailbox quotas in Exchange etc).

    2) Set the application pool identity to run as a user who has permission to do everything you need (e.g. a domain admin / service account with domain admin privileges). In PHP turn off impersonation with the following modification:
    fastcgi.impersonate = 1;
    to
    ;fastcgi.impersonate = 1;
    (if you do this, restart IIS).
    Any PHP calling PowerShell through the webpage will then launch PowerShell under the security context of the user you set to run the application pool.

    Obviously number 1 is a much more secure method but it can involve a significant amount of work. Our Exchange Administrator has spent many hours creating new RBAC groups to add our Service Desk users into, so they can perform certain tasks within Exchange. Permissions on other resources (such as in AD) are not so difficult to grant. Potentially number 2 would require little / no work on granting permissions, but you run the risk of someone compromising PHP and the PowerShell process to do anything your application pool user has permission to do…

    I hope that helps :)

    Robin

  6. Nice article.

    Does your account creation script just take static data from the initial php page, or can it get data dynamically ?

    I’m looking at a scenario containing multiple domains. I’d like the operator to be able to select the domain and upon doing so a list of relevant templates would be diplayed as the next choice.

    I am a total noob at php, but I have a windows form that can do the powershell side of things – would like to give it a web front-end.

    Any advice would be greatly appreciated.

    Thanks

  7. Hi Ratty,

    The documented setup is pretty basic as you suspect; a static HTML form is presented to the user, requires input from them, before being passed off to the PowerShell script to do the work. We have however modified a few pages to aim to be a bit more “dynamic” but unfortunately I don’t think either of them are appropriate as your requirement sounds slightly different.

    I think what you’re looking for is a mashup of PHP and JavaScript (AJAX) to do this nicely (i.e. user selects a domain from a drop down menu, and the options(?) below that change accordingly). It’d look slick and “web 2.0” but I don’t know how to do this off the top of my head (I’m sure I could figure it out but it would take some time).

    You could bypass learning JavaScript and work around this though to achieve what you’re after, but you’d likely require the user to submit a few times rather than dynamically updating the page without them refreshing. Before I can advise further though could you explain what you mean by “relevant templates”. What is a template exactly? Another HTML drop down menu? What happens after this? The more details you can give the better I can help.

    Thanks,

    Robin

  8. We have multiple domains with multiple sites. Plan to have an OU that contains “templates” for the base info for each site. By ‘template’ I really just mean a user account with all the basic info – address, description, home drive path etc. My current script copies this account, modifies with specific user info, copies to correct ou, creates mailbox and home dir folder on filesystem on correct server.

    Tried to make the script as generic as possible so if changes are made to AD, ie new site added, or different type of user required at a site, the script wouldn’t need to be changed as it collects the info on the fly when run.

  9. From what you’ve said I would create a HTML form with required generic fields and possibly two drop down menus. One for a list of your “templates” and one for a list of domains/sites. These would provide the identifiers for your PS script so it knows which domain/site/user account to “target” (copy and modify). I’m not sure if this is what you had in mind.

    You could make the drop down menus more dynamic in one of three ways:

    1) Write a separate scheduled PS script to output a text list of a) domain/sites and b) types of user accounts in template OU. Have PHP read those text files in on page load to generate the drop down. This is one way to do it: http://stackoverflow.com/questions/3767076/load-text-file-list-into-option-tags. The benefit of this approach is that PHP is very fast to list results from a text file. The downside is that you have to set up and maintain a scheduled task, and keep the text files somewhere.

    2) Have PHP run a PS script immediately on page load using shell_exec. For example I have a PHP page that when opened immediately runs the following PS script (which retrieves a list of AD groups and writes them out contained inside option tags): http://pastebin.com/pWtBHx4u
    I use the following HTML/PHP to run that PS script inside a HTML select tag and echo it out, which results in the generation of the menu options: http://pastebin.com/kDkMvBFr
    The PHP page will be slower to load because PS is run each time the page is visited, but the results in the drop down will be truly dynamic.

    3) Do the above using LDAP queries in PHP. I do not know anything about this.

    Good luck,

    Robin

  10. Thanks for the great advice and information.

    I’ll will actually try all of these and report back on my success (hopefully !)

  11. Great tutorial, I made my version of this work but once I turn on Windows authentication on iis the PowerShell scripts always error. Work great with basic authentication. Any ideas?

  12. Joe, I’m starting in on this too and I am also having problems with permissions. I haven’t yet figured out how to make it work consistently.

  13. Robin, if you are still monitoring this, can you modify this example script from Get-Process to Get-Printer for a remote server and tell me what you find? I have found that I can do Get-Process with no problems, but when I try the same thing with any sort of print management I get errors like the one below.

    Get-Printer : An error occurred while performing the specified operation. See the error details for more information.
    At D:\psweb\reset-driver.ps1:47 char:18
    + $initialdriver = Get-Printer -name $printer -ComputerName $computername
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (MSFT_Printer:ROOT/StandardCimv2/MSFT_Printer) [Get-Printer], CimException
    + FullyQualifiedErrorId : HRESULT 0x8007007b,Get-Printer

  14. Thanks for posting this. We’ve been talking about this but haven’t had the time to do much research. Previous searches came up null until the other day. Super helpful. Thanks. Any chance we could see some examples of what you guys are doing in production? Specifically around the area of how do I limit different users from seeing different scripts? For example, I don’t want my helpdesk workers using the scripts the Sys Admins uses.

  15. Apologies all, I’ve been flat out at work lately (and for the past two days I’ve been at Cloud World Forum in London which has been interesting!).

    Joe: We chose basic authentication specifically to force users to logon to the website with their “username-admin” accounts, as these have elevated privileges over their standard accounts (which they’re logged onto their desktops with). I hope you’re not logged onto your computers with powerful accounts ;)

    Anyway Windows auth will be using either NTLM or Kerberos, and so I suspect that PHP doesn’t have “access” to the username/password in order to impersonate the user and launch PowerShell as them. It’s a complicated area so I’ll have a chat with the Security geniuses at work and get back to you with a more certain answer.

    Chad: Can you confirm you’re using basic authentication for this? And the user you’re logging in as has admin permissions on the remote computer? What happens when you query without “-name $printer” to return all printers? Do you get a result at all?

    AJ: Based off your email address, you appear to work for an organisation that discriminates against sexual orientation (by lack of mention in its anti-discrimination policy) and opposes gay marriage. While I am happy this post may have helped you, I am not willing to actively help your organisation any further.

  16. I found a bug report that has already sent to Microsoft. It appears as though it is a second-hop authentication “problem”. I’m apparently not the first person to experience a problem with it, though I may be the first experiencing it through PHP. I re-wrote that portion to use WMI instead and it works well that way. Now that I have proven the concept works in our environment I’ve moved on to fleshing out the front-end and then I will go back and modify all our Powershell scripts to tie in.

    Thanks for the post, it has been invaluable.

  17. Thanks for posting this as I’m looking for something like this to deploy in my environment. I’m running into an issue with trying to import the active directory module. I have basic authentication setup, but keeping running into this error.

    WARNING: Error initializing default drive: ‘Unable to contact the server. This may be because this server does not exist, it is currently down, or it does not have the Active Directory Web Services running.’.

    Was wondering if you came across this before or if someone else did and what they did to get around it. I have a 2008 DC in place and the web service is enabled, I also have two 2003 DC in place.

    Thanks for any help.

    1. You are most likely running into a “second-hop” authentication problem much like I did with printers. If that is the case you have two options. Either configure CredSSP or run the site without impersonation and have the app pool run under a user with access to AD.

  18. Hello Robin.
    Very useful post! Currently working (digging deeper) on creating such internal website using PHP + IIS + PS. I have 1 comment and 1 question on this.

    Comment: You can output TABLE only (without BODY, HEAD tags) using ConvertTo-HTML -Fragment (in PS 3)

    Question: If i am executing some “complex” PS script, and i need to load several modules (QARS, Exchange, AD), or at least Exchange 2010 snapin. Just loading this module takes a lot of time. It happens because when PowerShell starting my script, it loads all staff i need, doing the action, and then PowerShell closes and unloads all loaded modules and snapins. Next time i am executing the same PS script it again loads all modules and snapins, and we are waiting again. So, actually, questions – How to load modules once and then just send a calls to loaded PS session\process OR make loading of modules faster?

    1. Hi Alexander,

      That’s pretty cool, thanks! I think the majority of our output is a little too complicated for that cmdlet and requires some programmatic logic but it might be useful for raw data structures / exports from other cmdlets. I’ll keep it in mind, assuming I can still tag tables with CSS classes so that it’s in keeping with our site design/theme :)

      Your question is an interesting one and not one that I’ve given much thought to simply because I hadn’t considered it possible; we too suffer from “load time” when loading the Exchange snapins. Similarly, scripts running against VMware vCenter take a long time as connecting to vCenter is pretty slow (~20 seconds perhaps). Loading the AD module and querying AD however is very quick. I’m sure a .Net developer would be able to provide better insight into whether this was possible – perhaps a running .Net service and associated process. Sorry I can’t be of more help. Have you posed this question on StackOverflow?

      Robin

    1. Hi Alex. I’ve had a read of your post. Funnily enough I was intending to whip something up for our Office 365 services very soon (we’re just migrating Staff to O365 email). I’ll see what happens when I try and run a similar type of command and get back to you.

    2. Ok, so I did a quick and simple test and it worked for me. Can you execute this successfully (changing links as necessary of course)?
      Connection file: http://pastebin.com/ZWE8GyT4
      File to call via shell_exec: http://pastebin.com/dHNnpzpa
      PHP: http://pastebin.com/REu4skLn

      If this works, then we know it’s an issue with remote sessions. I’ve not used them yet but a colleague has and I’m pretty sure he’ll be expecting to use them via the website we have. I might try running your code in our environment and see if it works now…

        1. OK, I see. You forgot to import the MSOnline module

          All I did was add:
          Import-Module MSOnline
          before the connection bit.

          That works if I launch it directly from the powershell. If I launch it from PHP, I get this error:
          testing o365 connection
          Connect-MsolService : Exception of type ‘Microsoft.Online.Administration.Automa tion.MicrosoftOnlineException’ was thrown. At C:\powershell\test\conn.ps1:7 char:20 + Connect-MsolService <<<< -Credential $cred -ErrorAction Stop + CategoryInfo : OperationStopped: (:) [Connect-MsolService], Mic rosoftOnlineException + FullyQualifiedErrorId : 0x80070002,Microsoft.Online.Administration.Autom ation.ConnectMsolService

        2. Having looked at your code a little more I think you’re creating a remote PS Session in order to access the MSOnline cmdlets remotely, would that be right?

          I’ve installed the Windows Azure Active Directory Module for Windows PowerShell (http://technet.microsoft.com/en-us/library/jj151815.aspx) and I’m running this on Server 2012 R2 (PowerShell v4) now. As you might already know PowerShell v3 and upwards no longer require you to import modules (all cmdlets are available after the shell is launched).

          If you’re able to install the module from the link above you might be able to avoid using PS Sessions for some commands (pretty sure you’d be able to do a password reset), and it’d be much quicker as well. If you are running PowerShell v2 try my script again but with Import-Module MSOnline as the top line in the connection file.

        3. Ah, I replied without seeing your earlier reply. Have a go installing the WAAD PS module. If that doesn’t work I’m happy to setup a teamviewer session with you so I can help that way (I woke up to this after a relative stranger helped me debug some JavaScript – so much easier than back and forth messages). Email me at: robin @ this domain and we can sort that out.

          Edit: fixed the issue using remote support :)

          1. I’m having what seems to be this exact same problem. I can run the powershell script on the server just fine but when I call it from a PHP page I get the same error message that Alex described. What did you do to fix it exactly?

          2. Hi Tyler. It was a while ago now so I forget exactly what the problem and solution was. Are you trying to use “Connect-MsolService” too?

          3. Hi Robin. I’m grateful for your reply on this rather old thread! Yes, I am trying to use the same cmndlet. I’m on IIS 8. Server 2012 R2. Running power shell v4 and the latest Azure Powershell Module. I can connect fine through power shell but when the command is called through IIS I get the same message that Alex pasted above. Yours was the only post I found about this particular problem. If you have got any hints I am all ears!

          4. Hi Tyler. Sorry for the delay in getting back to you! I’m really not sure how we got it working for Alex. I have him on Facebook so could ask, but coincidentally I hit the very same issue today (we’d not been using Connect-MSolservice ourselves) and have had to troubleshoot it myself. I noticed that the “Microsoft Account Sign-in Assistant” service was set to “Manual (Triggered Start)”, and wondered if it could be something to do with that not being triggered when running via IIS. I started the service manually, and executed my PHP page which called PS to connect. It worked. I also then stopped the service and added Start-Service "Microsoft Account Sign-in Assistant" to my PS script and that did the job also. Let me know if this solves the issue for you :)

          5. Thanks again for your reply Robin. I struggled with this for some time before giving up and switching to Apache for windows instead of IIS which works just fine. Some tidbits of information that I learned though: When IIS called the Connect-Msolservice cmndlet there was an event log error about Component Services permissions. I eventually corrected that but it didn’t help. The only difference that I saw before I gave up was returning “$env:userdomain\$env:username” in Powershell through IIS had the computer object running the script and running the same script in Apache has the user running the script. I can only surmise it is permissions somewhere. I can only bang my head against that wall for so long before I look for alternate solutions…Like I said, things work great in Apache.

  19. Thanks for the tutorial! I was wondering if you display a status page while your powershell scripts are running in the background? Something like a “please wait while your request is executed…” page. Also, do you have any experience returning results dynamically as the script is running? I am running a PS script as jobs on different servers and would like to get the results from each server as they each complete.

    1. I used jQuery BlockUI to block the screen so buttons could no longer be pressed and it would should show “Please Wait” while the script was running.

      As for your second question, I didn’t get results dynamically, but it shouldn’t be too hard. You would just need some place that the results were being written to and then query that with an ajax call to refresh the page. I used SQL Express extensively to store a log of log data, I would think it would work well in this instance too.

  20. Hi, I tried to execute get-vmnetworkadapterextendedacl with method but it gives null result. However, if i execute it powershell ise, then i shows correct output. any idea ?

  21. I need your help.
    I followed your instructions but I got a problem.
    Example that you gave to me on the server works fine.
    I want to using php and powershell commands to create users in AD & Exchange 2013. when run powershell script on the webserver, then all goes well with no problems, But I stuck with php scripts. I run Webs and enter the data required for the variable, then I get a time out from the web server. Here’s an example that I created:

    [admin: removed]

    1. Please don’t paste full code into the comments. WordPress formatting can strip out certain characters, and it also makes the page harder to read for others. Use a website like Pastebin.com instead and include a link in your comments :)

      First thought: your PS script is using “Read-Host”. When you call the New-Employee function it’s waiting for an answer from the host and PHP is not providing this. Assuming PHP has passed all the required information to the PS script, you can just call the function with -firstname $name -lastname $lastname, etc.
      Inside the function, remove references to Read-Host. That would be the most simplistic way to get it working from what I can see.

      1. If I understand you correctly, I would need to replace the following in ps script:
        $firstName = (Read-Host “Ime”),
        whit this:
        -firstname $firstname

        or did you mean to replace the php scripts?
        $query = shell_exec(“powershell -command $psScriptPath -firstname ‘$firstName’ -lastname ‘$lastName’ -UserPrincipalName ‘$userPrincipalName’ -initialpassword ‘$initialpassword’ -FromEmailAddress ‘$FromEmailAddress’ -ToEmailAddress ‘$ToEmailAddress'<NUL");

        1. Inside the New-Employee function replace
          $firstName = (Read-Host “Ime”),
          with
          $firstname,
          and so on.

          Call the function like this:
          New-Employee -firstname $firstname
          and so on.

  22. Hi Robin, thanks for the great tutorial, works well… But I got some problem ;)

    I’m using PHP to create a WebGUI to execute a powershell script.

    Although the script run very fastly when I execute it directly from a commandline, when i call it from PHP it takes ages (1 – 2 minutes) to return anything, and most of the time just reaches the timeout.

    This would be the way I call my script in PHP :

    $psScriptPath = “C:\\powershell_scripts\\create-vm.ps1”;
    // Execute the PowerShell script, passing the parameters:
    $query = shell_exec(“powershell -command $psScriptPath -vmname ‘$vmname’ -username ‘$username’ -os ‘$os’ -landesk ‘$landesk’ -sla ‘$sla’
    -supervision ‘$supervision’ -env ‘$env’ -owner ‘$owner’ < NUL");

    The Powershell script just connects to my vCenter server and disconnect for now, but it still takes ages to return anything.

    I've tryed to check IIS configuration but can't manage to find what could cause that answertime. I also tried to close most of my Powershell variables, put it doesn't seem to have any influence seen that, on its on, the PS script run pretty fast (<1s).

    The timeout error i get is "fastcgi process exceeded configured request timeout", on Default Web Site (which is strange as I don't host my PHP page on the default web site). I tried putting PHP timeout to 10 minutes, but wont work either, and basically not solving my problem as I'd appreciate not waiting 10 minutes before having an answer… ;)

    I guess I'm missing some option of a kind, or something, but can't figure out what.

    Please ask if you need more details about anything,

  23. Hi Robin,

    Create creat post, if you start with this the limits are endless!

    I hope you are still monitoring this treat, i was wondering if its possible to define some PHP variable’s in powershell so i can work with them in PHP. At this way i am albe to skip some scripts.

    Thank

    Michael

    1. Hi Michael,

      Indeed. This methodology has changed the way we support our customers, with webpage “tools” developed for front line customer facing support staff, through to the third level support teams.

      With regard to your question, I’m not sure I understand. The implementation described in this post covers PHP executing a PowerShell script, capturing the output, and returning it into a single PHP variable and printing/outputting that to the screen.

      If you want the return data from your PowerShell script, to define variables in PHP, then you may need to return data in a structured fashion (e.g. JSON). Keep it mind your PowerShell script would have to return only JSON for this to work effectively. You’d then have to parse the results within PHP, to assign values within the JSON, to your PHP variables.

      I hope that helps.

      Regards,

      Robin

  24. Hi Robin,

    Thanks for the feedback. Yes is does help.. will try to continu with it,

    Another question, is there a way to run the powershell script always as administrator ?

    Thanks,

    Michael

    1. I believe that if you’re already an Administrator on the machine, that it’ll run as administrator. If you put the following into your script, it will tell you if that is the case or not:

      ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
      – this will return true if it’s running as Administrator or false if it isn’t.

      Another alternative is to create a virtual directory within IIS, and run that as a specific user that *is* an Administrator on the machine. Any PHP page (and PowerShell script launched from it) will run as that user account, rather than the user logged into IIS.

      Regards,

      Robin

  25. How do you set the remote execution policy, so it can execute the script from php? i keep getting this error:

    File F:\get-process.ps1 cannot be loaded because running scripts is disabled on this system.

    my execution policies looks like this:

    Scope ExecutionPolicy
    —– —————
    MachinePolicy Undefined
    UserPolicy Undefined
    Process Unrestricted
    CurrentUser Unrestricted
    LocalMachine Unrestricted

    1. Hi Mads. I’m a little unclear what you mean. There isn’t a “remote execution policy” as such, but there is a system execution policy. The PHP and PowerShell live on the same server so executing it shouldn’t be a problem as long as your execution policy isn’t set to “restricted”. Try Set-ExecutionPolicy RemoteSigned and that should be sufficient.

      1. Hi Robin, thans for replying. When executing it through the php script i get this error, on the php page:

        File F:\get-process.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.At line:1 char:1 + F:\get-process.ps1 -username ‘Administrator’ + ~~~~~~~~~~~~~~~~~~ + CategoryInfo : SecurityError: (:) [], PSSecurityException + FullyQualifiedErrorId : UnauthorizedAccess

        i have tried both Set-ExecutionPolicy RemoteSigned and Set-ExecutionPolicy Unrestricted, with out any luck.

        1. Hi Mads. What is your OS? You may need to set the execution policy on the x86 version of PowerShell (2008 R2 has both an x86 and x64 version of PowerShell, and these have separate policies). If you’ve downloaded the default build of PHP from PHP.net (which is x86 – they never used to do x64 builds but they do now), then that will launch the x86 version of PowerShell via shell_exec. This can bite people in the butt because when you type “PowerShell” into the start menu, the first result is the x64 version (which just displays as “Windows PowerShell”) and that’s the one you’re likely to launch and set the execution policy on!

          Checking our production and development servers, the only scope that is defined is the LocalMachine (and we have functioning servers with both unrestricted and remotesigned policies). If you’re still having trouble, I’ve used TeamViewer in the past to help a few people so if you’re really stuck and would like me to take a look drop me an email at robin @ this domain and perhaps we can arrange some remote help.

          1. I am running it on a windows 2012 server. I installed php through microsoft webplatform form installer. You were right though, there were a x86 of powershell on the system. When i added the execution policy to it, it worked like it should. Thanks for taking you time. One last question. I am about to rewrite a couple of my powershell scripts, to worh though a web portal. Are there any kinds of limits on how many varriables i can pass though? and what about spaces in variables? ie

            powershellscript.ps1 -v1 “varriable one” -v2 “admin” would that work? and are there any kinds of limits on how many you can add? i hope it makes sense.

          2. Great :) I don’t know whether what you wrote would work exactly (that’s how you’d call a script from the PowerShell command line, certainly!). From PHP, you can see my example line is as followed:
            $query = shell_exec("powershell -command $psScriptPath -username '$username'< NUL");
            Note the use of quotation marks to encapsulate the entire line, and apostrophes to contain the PHP variables. There is no problem with spaces in your variables, and I've yet to hit a limit on how many you can pass to the PowerShell script as parameter values.

            One useful tip to see what has been passed is to monitor Task Manager on the "Details" tab (on 2012+). If you right click any of the columns, you can add an additional one called "Command line". When PHP runs shell_exec, and calls PowerShell, you'll see the PowerShell process appear and the exact command line that it's running with will display in the column. Of course if your script doesn't do much it will appear and disappear very quickly, but adding a Start-Sleep cmdlet can help delay this. Good luck!

  26. How would you solve permission problems, when trying to acces certain functions in windows? I am trying to write a website deploy function, and i got a working powershell script. I have change my php version to php64bit in order to get the function to acces iis to work. How ever i keep getting acces errors when trying to do something with it. Do you have any ideas?

    Cannot retrieve the dynamic parameters for the cmdlet. Filename: redirection.config Error: Cannot read configuration file due to insufficient permissions At D:\selfservicescripts\selfservice_create_cleansite.ps1:35 char:1 + New-Website -Name $sitenavniis -Port 80 -HostHeader $weburl -PhysicalPath $iispa … + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~ + CategoryInfo : InvalidArgument: (:) [New-Website], ParameterBin dingException + FullyQualifiedErrorId : GetDynamicParametersException,Microsoft.IIs.Powe rShell.Provider.NewWebsiteCommand

  27. Hi People,

    I am unable to send characters like é to powershell. threu a php form, any suggestions ?

    Thanks Mike

  28. Hi sir!

    I created a simple php page to unlock a user in Active directory. The script was already working in windows server 2008 but since we upgrade our servers to 2012. It is not working anymore. I was able to load the page but unable to parse data to powershell script. Kindly help me in this matter.

    Thanks sir!

  29. Hey Rob,

    I have the same problem as one of the previous poster : the UnauthorizedAccess exception when running the script.

    I did some troubleshooting but I’m really stuck. Running Win2012 R2. Here are my troubleshooting steps :

    1/ I’ve setup basic authentication, I log in as a domain user.

    2/ I execute a shell_exec(“set”) in the PHP script and the output for USERNAME is “THE_SERVER_NAME$”. Strange, isn’t it?

    3/ I execute a shell_exec(“powershell whoami”) and the output is the username I’ve logged in with.

    4/ I execute a shell_exec(“powershell -command $psScriptPath”) with $psScriptPath set as “C:\www\webps\scripts\whoami.ps1” which is a simple script executing a whoami and doing a Write-Output and I have the “UnauthorizedAccess” error message from Powershell.

    5/ Doing this last attempt, I monitor the task manager and see that cmd.exe is run with the username I’ve logged in with (cmd.exe /c “powershell -command C:\www\webps\scripts\whoami.ps1”)

    6/ The execution policy is set to unrestricted both for the x86 and x64 version of Powershell.

    7/ If I log in on the web server with the domain user and run the script, it works.

    8/ If I do a PSSession on the server with the domain user and run the script, I have the “UnauthorizedAccess” error message

    9/ Still doing a PSSession, If I run whoami, it works.

    10/ The user has read/execute permission on the script.

    11/ If I run all those steps as a domain admin, everything works.

    12/ If I had the domain user as local admin, still doesn’t work

    I’m out of ideas… please help… What do I forget ?

    1. Correction, if the user is local admin, it works. It must be somehow related. Strange that it works without admin rights when the user is logged on…

  30. I got this working and that’s real nice. I was attracted to this article because I had hit a wall previously doing something like this with Apache on Windows Server 2008. In that instance I could easily pass a parameter from an html page, let’s say a USER ID, and capture it as a variable in the called PHP page.

    The syntax model I was using to run Powershell commands in PHP is basically this:
    exec(‘powershell.exe -command “COMMAND ” <NUL ' , $my_array2 );

    Now using that syntax to run a .PS1 script works beautifully. I could do simple GET commands easily. But passing a variable (containing a USER ID) as a parameter to do a lookup using that syntax failed for me. I learned all about utilizing escape characters as I thought something like DOMAIN\USER ID was what was tripping me up. Now I wouldn't dream of asking for help on that situation, but rather do you have any examples of doing something like that that can be shared?

    This IIS model makes it look like it will simpler to capture and do parameter passing. Is that correct?
    -thanks

    1. Hi Tom,

      So, from my example you can see the syntax is of the format:
      shell_exec("powershell -command $psScriptPath -username '$username'< NUL");
      So this is calling a script, with a parameter block containing one parameter called username. Are you wanting to pass something like "domain\userid" within $username? Is that the problem?

      I'm not entirely sure how exec(‘powershell.exe -command “COMMAND” functions with passing an array on the end after command (I've not tested this), but using my method you can easily build up the line you wish to pass to the PowerShell script, linking form variables to your parameters. For instance, say your form had two fields with names of "username" and "password". On post, you'd check the $_POST variable for what you wanted to capture like so:

      $psArgs=array(
      '-File '.escapeshellarg($psScriptPath),
      );
      if(isset($_POST["username"]) && !empty($_POST["username"])){
      $psArgs[]=' -username'.escapeshellarg($_POST["username"]);
      }
      if(isset($_POST["password"]) && !empty($_POST["password"])){
      $psArgs[]=' -password'.escapeshellarg($_POST["password"]);
      }

      and then when you're ready call PowerShell like so:
      shell_exec("powershell ".implode(' ',$psArgs)." < NUL");

      Hope that helps.

  31. I have been using this a lot, and wand the share with you how to return multiple value’s with a single PowerShell script.
    PHP script:
    $user = $_GET["user"];
    $psScriptPath = "C:\\OD-Get-User.ps1";
    $query = shell_exec("powershell -command $psScriptPath -user $user");
    $decoded = json_decode($query);

    Powershell script:
    param(
    [string] $user
    )
    $name = Get-ADuser -Identity $user -Properties *
    $voornaam = $name.GivenName
    $achternaam = $name.SurName
    $Weergavenaam = $name.Displayname
    $functie = $name.Title
    $afdeling = $name.Department
    $kantoor = $name.Office
    $telefoonwerk = $name.telephoneNumber
    $mobiel = $name.mobile
    $mail = $name.mail
    $logon = $name.LastLogonDate
    $ProfilePath = $name.ProfilePath
    $PasswordExpired = $name.PasswordExpired
    $PasswordLastSet = $name.PasswordLastSet
    $PasswordNeverExpires = $name.PasswordNeverExpires
    @{Gebruikersnaam="$user";Voornaam="$voornaam";Achternaam="$achternaam";volledigenaam="$Weergavenaam";functie="$functie";afdeling="$afdeling";kantoor="$kantoor";telefoonwerk="$telefoonwerk";mobiel="$mobiel";mail="$mail";login="$logon";profiel="$ProfilePath";PasswordExpired="$PasswordExpired";PasswordLastSet="$PasswordLastSet";PasswordNeverExpires="$PasswordNeverExpires"} | ConvertTo-Json

    Now if you want to use the variables you can just use “$decoded->Voornaam;” in your php script

    1. Hi Michael,

      Thanks for the comment! Happy to say that for version 2 (which I launched early this year), we are also using a hybrid of the original approach and JSON objects in the exact way you’ve described.

      Regards,

      Robin

      1. Hi,

        Thanks for this script, very useful!
        I would like to show the result of the query and then do another query in the context of that query, for example, I would like to search for an AD user, then present info for that user and then be able to change the AD password of that user (without needing to enter username), how would you approach this?
        I guess that using JSON objects would make the task easier?

        1. Hi Robin,

          Sure, using JSON objects would absolutely be one method of doing this; in fact, it’s what we do now albeit with some heavy JavaScript on top. I’ll try and describe what we do just in case it gives you some inspiration, but unfortunately I can’t put full code samples up because it’s not generic enough.

          One page our service desk use is called “Set Display Name”. The first field accepts a username and once populated by the end user they click a hyperlink just to the right of the field called ‘Get Data’. This calls a JavaScript function (called ‘FindFrom’) which effectively POSTS the username and value back to the same PHP page, by way of an AJAX call ($.ajax). At the top of the PHP page we detect this condition with something along the lines of: if($_POST["submit"]=='getdata'){ and if(isset($_POST['username'])){.

          From here we call our singular underlying PowerShell script (I wanted to keep a 1-to-1 relationship between PHP and PS1 for manageability). With some parameterset logic we can determine what kind of call to the PowerShell we’re making (is it a GetData or a SetData?). If PowerShell is called with just a ‘username’ parameter it fulfils the requirement for the ‘GetData’ parameterset and we detect this in our PowerShell script body section using if($PSCmdlet.ParameterSetName -eq 'GetData'). Within this if statement we lookup the object in Active Directory, grab the properties we require, construct our $object (hashtable) and return this back to the front end with ConvertTo-Json -InputObject $output -Compress. At this point, under this condition, the PHP code terminates.

          So let’s recap: We’ve had a user visit the page, enter a username, click ‘Get Data’ and JavaScript has made a behind the scenes POST to the same PHP page which has called a PowerShell script with a singular username and spat back a load of JSON. So far this is all transparent to the end user (other than a pop up using jQuery blockUI that we’ve added to say we’re ‘getting data…’).

          After the return of the JSON data, the FindFrom function can continue. JavaScript uses the parseJSON method to parse the result, loop through the data, and use jQuery selectors to match up elements within the JSON object to the fields on the webpage, then populates them with the values. Imagine our PowerShell $object has a property called ‘forename’ with a value of ‘Robin’. In the loop, jQuery would attempt to select the field with name=’forename’ and then set the value of the field to whatever we obtained from the AD object.

          Ultimately we end up with a form that has all fields populated with data from the resource that we’re querying (whether it’s an AD object, a virtual server, or something else). We tend to use JavaScript to restrict the fields we don’t want them to edit as some fields are populated purely for informational purposes (of course front end validation should not be your only security method). When the end user clicks the ‘submit’ button, this calls the same PowerShell script and it triggers the parameter set ‘SetData’ which makes the edits to the resource and returns standard text.

          Believe it or not I’ve tried to simplify that as much as possible (our pages do a bit more than this in actuality) so I hope that makes sense. Whenever I revisit the code I have to remind myself of how it works!

          Regards,

          Robin

          1. Thanks for your elaborate answer Robin! I think you did a good job simplifying it :-)
            I’ll try and see if I can get it to work :)

          2. Hi again!
            I have studied jquery and AJAX but I cannot get this to work.
            The problem is that Ajax doesnt get any reponse from the PS-script. In the debugging tools I can see that the PHP page just says “pending” (under Network).
            If I open the PHP page instead of the HTML I get the expected response printed on the page (the PS just contains a simple Write-Output since I wanted to strip down the code as much as possible).
            If I just do a echo “test”; in the PHP (and removing the query to the PS-script) Ajax gets the expected response.
            So the problem is that for some reason, when I execute a PS-script in PHP, Ajax doesnt get any response.

            JS:
            $( function(e) {
            $( ‘#userform’ ).submit( function() {
            var username = document.getElementById(“username”).value;
            var formData = $(this).serialize();
            alert (formData);
            if (username == ”) {
            alert(“Fyll i”)
            }
            else {
            $.ajax({
            url: ‘testuser.php’,
            dataType: ‘text’,
            type: ‘post’,
            data: formData,
            success: function(resp) {
            alert(resp);
            }
            });
            }
            });
            });

            PHP:

            PS:
            Write-Output “testPS”

            I have tried using JSON as well but that didnt work either, so I tried simplifying the code as much as possible.
            Any ideas? :)

          3. Hi again,

            For some reason the PHP code didnt show in my last reply. So here is the very much simplified code:

            $query = shell_exec(“powershell -command C:\\inetpub\\wwwroot\\ClientManagement\\ps\\usertest_ajax.ps1”);
            echo $query;

          4. Hi Robin,

            Unfortunately JavaScript is not a strong area for me; I used an in house developer to write most of that bit. I’m certainly not fluent enough to troubleshoot partial code fragments and as you’ve found WordPress comments are not great places for code segments (complete or partial)!

            If I get any free time over Christmas then I’ll try and produce a really stripped down version that works, and drop you an email. I should set expectations now though – I have a house renovation ongoing, full time job, and I’m trying to help my gf with her business so my sincerest apologies but this will have to be a lower priority. Best of luck though. Hopefully you crack it before I get around to looking at it haha.

            Regards,

            Robin

          5. Hi Robin!

            No worries, I somehow managed to solve the issue with the help of a co-worker. For some reason the AJAX request entered the pending state when the request was set to synchronous (this is default as far as I understand), when we added the parameter async: true; to the AJAX request it started working. The wierd thing though, is that when I remove async: true it still works. I am so confused but I am happy as long as it works :-)

            I have managed to get it to work the way you described above (getting and setting data etc) and I will continue to work on this. It is so powerful to be able to directly control AD attributes from a web page and it feels like it has so many use cases.

            Anyway, make sure to spend and enjoy your time with your GF instead :-)

            Thanks!

Leave a Reply