I have a Powershell script which when I run directly on the Web Server (Windows 2008R2, IIS) completes successfully. I am now trying to launch the script from a php page. The script launches and I can see the process in Task Manager just fine.
When I call the script using shell_exec from a php page, it fails with the error
"Exception calling "FindOne" with "0" argument(s): "An operations error occurred."
I can see from Task Manager that the script is running as the user IUSR. I'm pretty sure that this is the reason the script is failing, as to complete successfully it needs to be run as a domain admin.
I am calling the script with the following command
$username = the currently logged in user on the page (authenticated against AD)
$newPword1 = new user password
$query = shell_exec("powershell -command $psScriptPath '$username' '$newPword1' < NUL");
Powershell Script:
#*=============================================================================
#* PARAMETER DECLARATION
#*=============================================================================
param([string]$username,[string]$pword)
#*=============================================================================
#* 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
#*=============================================================================
#* SCRIPT BODY
#*=============================================================================
$root = [ADSI]'LDAP://<server>/<DNtoOU>'
$searcher = new-object System.DirectoryServices.DirectorySearcher($root)
$searcher.filter = "(&(objectCategory=person)(sAMAccountName=$username))"
$user = $searcher.findone()
$userDN = $user.path
$user=[ADSI]$($userDN)
$user.SetPassword($pword)
$user.setinfo()
write-host "success"
Is it possible to pass a Domain Admin username/password to run powershell as using the above command, thereby allowing the script to run successfully?
One of several ways is to use PowerShellRunAs.
Edit: It is documented, you can use the powershell script as-is or modify it to suit your needs (e.g. not using a password file).
In more detail, instead of this:
shell_exec("powershell -command $psScriptPath …")
You could use this:
shell_exec("powershell -command "Start-Process powershell.exe -argumentlist '-command $psScriptPath …' -Credential 'TheDomain\TheUser'")
But to avoid the password prompt, use PowerShellRunAs as per the documentation:
shell_exec("powershell -command "Start-Process powershell.exe -argumentlist '-command $psScriptPath …' -Credential (.\PowerShellRunAs.ps1 -get contoso\svc_remoterestart \\fileserver\share\file.pwd)")
Alternatively, you could incorporate the start-process … -credential … into your own script. That way you can keep your shell_exec call simple as before, and only a limited part of your script will run under the user with different (higher) privileges.
Should you use the script as is, remember to first run it with -set instead of -get, in order to set the password file.
Related
Firstly I apologise but I am pretty new to PHP and PowerShell, we all have to start somewhere! I am creating a utility where everyday IT tasks can be performed from a central web based console. I have managed to query and report on things like password expiry by executing PowerShell scripts but have got stuck on unlocking accounts. I query AD and return a list of locked users with a button next to each user to unlock them. This button posts to a php page which runs another powershell script to unlock the user.
php page is:
<?php
// Get the variables submitted by POST in order to pass them to the PowerShell script:
$lockeduser = $_POST["unlock"];
// Path to the PowerShell script.
$psScriptPath = "C:\\code\\psphp\\ps\\unlock.ps1 $lockeduser 2>&1";
// Execute the PowerShell script:
exec("powershell -command $psScriptPath",$out,$ret);
echo "<pre>";
print_r ($out);
print_r ($ret);
echo "</pre>";
?>
As you can see I'm trying to capture any output but at the moment the page is just hanging.
PowerShell script is:
param([string]$lockeduser)
Import-Module ActiveDirectory
$adminacc = "*myadminaccount*"
$encrypted = Get-Content c:\password1.txt | ConvertTo-SecureString
$credential = New-Object System.Management.Automation.PsCredential($adminacc, $encrypted)
Unlock-ADAccount -Identity $lockeduser -Credential $credential
If I echo the command before passing it to PS it looks fine and can be executed directly from PS.
Edit: This is something to do with exec (or shell_exec) causing an issue when the PS script is setting credentials. If I remove that part of the script i.e.
param([string]$lockeduser)
Import-Module ActiveDirectory
Unlock-ADAccount -Identity $lockeduser
it runs and returns that the script failed due to
Insufficient access rights to perform the operation
Has anyone come across this before, I have searched for anything on this to no avail. Thanks!
Further edit
After a bit more testing it is this PS code that doesn't work
$encrypted = Get-Content c:\password1.txt | ConvertTo-SecureString
If I change the method to
$password = ConvertTo-SecureString "My Password" -AsPlainText -Force
it works with no problems. Plain text passwords in files are obviously not something I want to use. Can someone test and see if they get the same result?
So it turns out that it was all just me being a newb. Executing the script via the php page must not be running it as my account. My account was the one that set the credentials and stored it in the file so is the only one that can decrypt it. I have changed the convertto-securestring method to use keys instead of default and it now works.
I have a few functions and variables defined for all machine users like so:
Set-Content $Profile.AllUsersCurrentHost (Get-Content path/to/myprofile.ps1)
Let's call one of my defined functions Do-Stuff.
This works very well. And anytime Powershell console is called up, and you type "Do-Stuff"+ENTER, it works.
Now I have tried to call this function from PHP a few ways, and I'm having a couple of problems. Consider this:
$res = shell_exec( 'Powershell Do-Stuff');
print_r($res);
What I get is this error:
Do-Stuff: The term 'Do-Stuff' is not recognized as the name of a
cmdlet, function, script file, or operable program....
I've tried also:
$res = shell_exec( 'Powershell -File path/to/script.ps1');
print_r($res);
If the file script.ps1 does contain Do-Stuff or any of the other defined functions, what I get is the same type of error message.
Now what this tells me is that the calling PHP script is NOT recognized as a Windows machine user, and the currently loaded $Profile is NOT being applied.
So what's the solution to this? How can I get the loaded profile for current user or all users to be applied to the running PHP script?
I have a workaround for this issue, using -NoProfile, then dot-sourcing the Profile file.
In the below example, the profile file is includes.ps1:
$cmdlet = 'Do-Stuff';
$cmd = 'Powershell.exe -ExecutionPolicy Bypass -NoProfile -Command "& { . \includes.ps1; '.$cmdlet.' }"';
shell_exec($cmd);
It may prove useful for you to enclose this in a reusable function:
<?php
function run_ps_command($cmdlet, $async=true){
$cmd = 'Powershell.exe -ExecutionPolicy Bypass -NoProfile -Command "& { . \Includes.ps1; '.$cmdlet.' }"';
if($async){
$WshShell = new COM("WScript.Shell");
$res = $WshShell->Run( $cmd, 0, false);
}else{
$res = shell_exec($cmd);
}
return $res;
}
?>
The $async argument allows you to run this command without having PHP wait for the output of the PowerShell script. The advantage is that PHP code execution is faster, the disadvantage is: you've no idea if your script ran successfully, or got into any trouble on the way ;)
I have been wracking my brain and my pc on this one, hoping someone can help me out here.
I have a PHP site that needs to execute a powershell script which in turn executes and executable.
the PHP that calls the .ps1 is
define('updaterPath','C:\\artemis\\DbUpdateUtility1.0.12.2');
define('outputLocation',updaterPath.'\\Output');
$CMD = updaterPath . '\\ArtemisXmlUtil.exe';
$action = 'import';
$args= ' -f' . outputLocation;
$psScriptPath = '.\\get-process.ps1';
$query = shell_exec("powershell -command $psScriptPath -cmd '$CMD' -action '$action' -paras '$args' < NUL");
my .ps1 script is:
param(
[string]$CMD,
[string]$action,
[string]$paras
)
& $CMD $action $paras
Right now, when I echo the full command, it looks perfect and can be copied and pasted into powershell and runs successfully, but when I try to actually execute it from php, it runs with only the $CMD variable and ignores the $action and $paras.
I tried concactenating the variables all into 1 string, but that fails as not even being able to see the executable.
the full command shouls look like:
C:\artemis\DbUpdateUtility1.0.12.2\ArtemisXmlUtil.exe import -fC:\artemis\DbUpdateUtility1.0.12.2\Output
Ok, figured this one out on my own.
The problem is how the command was being executed. Using the "&" forced powershell to ignore the parameters being pass into the script. The correct way, since the command is an executable anyhow, was to build the entire command as a string and pass it all at once to the ps1 file and then change the ps1 file to use the invoke-expression commandlet.
param(
[string]$CMD
)
Invoke-expression $CMD
I need to start php process from shell on remove server with some arguments, so i thought that it should be a nice idea to make REST API, that executes some function when user performs GET request.
I wrote a simple bash script for testing and figured out that command-line argument is not being specified, when calling this script from website:
shell_exec('/var/www/test.sh 123')
Bash script source:
#!/bin/sh
echo $1;
When calling this bash script from root (or other existing user) it correctly shows argument it has received. When i call this script from website (that is running under user www-data under apache2), it returns nothing:
Also, if i execute this bash script in my console under www-data user, it also returns nothing:
su -c '/var/www/test.sh 123' www-data
Also i've tried to start process from different user from php (is supposed that this will not work for security reasons, but just in case):
$result = system("su -c '/var/www/test.sh 123' sexyuser", $data);
// var_dump($result): string(0) ""
// var_dump($data): int(1)
So, what privileges should i give to www-data user to run process under php?
You should let php run the script and handle the results
check php.net on exec for example http://www.php.net/manual/en/function.exec.php
//called by example.com/myshell.php?day=today&k=y&whatever=youwant
$arguments = implode(" ", $_GET);
$lastline_of_exec_result = exec ( "/your/command.sh ".$arguments); //sh called with today y youwant
echo $lastline_of_exec;
Where $arguments are the stringified list of ALL information your script got from GET arguments
if you want a ore precise in and output, try this:
//called by example.com/myshell.php?day=today&k=y&whatever=youwant
$argument = $_GET['whatever'];
$output = array();
$last_line = exec("your/command.sh ".$argument, &$output); //sh called with youwant
foreach($output as $line)
echo $line."<br/>".PHP_EOL;
or of course (with shell_exec)
$argument = $_GET['whatever'];
$output = shell_exec("your/command.sh ".$argument);
echo "<pre>".$output."</pre>";
make sure (shell_)exec is not listed under disable_functions in your php.ini
I need to build a system that a user will send file to the server
then php will run a command-line tool using system() ( example tool.exe userfile )
i need a way to see the pid of the process to know the user that have start the tool
and a way to know when the tool have stop .
Is this possible on a Windows vista Machine , I can't move to a Linux Server .
besides that the code must continue run when the user close the browser windows
Rather than trying to obtain the ID of a process and monitor how long it runs, I think that what you want to do is have a "wrapper" process that handles pre/post-processing, such as logging or database manipulation.
The first step to the is to create an asynchronous process, that will run independently of the parent and allow it to be started by a call to a web page.
To do this on Windows, we use WshShell:
$cmdToExecute = "tool.exe \"$userfile\"";
$WshShell = new COM("WScript.Shell");
$result = $WshShell->Run($cmdToExecute, 0, FALSE);
...and (for completeness) if we want to do it on *nix, we append > /dev/null 2>&1 & to the command:
$cmdToExecute = "/usr/bin/tool \"$userfile\"";
exec("$cmdToExecute > /dev/null 2>&1 &");
So, now you know how to start an external process that will not block your script, and will continue execution after your script has finished. But this doesn't complete the picture - because you want to track the start and end times of the external process. This is quite simple - we just wrap it in a little PHP script, which we shall call...
wrapper.php
<?php
// Fetch the arguments we need to pass on to the external tool
$userfile = $argv[1];
// Do any necessary pre-processing of the file here
$startTime = microtime(TRUE);
// Execute the external program
exec("C:/path/to/tool.exe \"$userfile\"");
// By the time we get here, the external tool has finished - because
// we know that a standard call to exec() will block until the called
// process finishes
$endTime = microtime(TRUE);
// Log the times etc and do any post processing here
So instead of executing the tool directly, we make our command in the main script:
$cmdToExecute = "php wrapper.php \"$userfile\"";
...and we should have a finely controllable solution for what you want to do.
N.B. Don't forget to escapeshellarg() where necessary!