Blog Archives
Managing Windows services with Symfony/Process and PHP
Sometimes I need to stop/start remote Windows services with PHP. It’s quite easy to do it with net commnand. This command is a tool for administration of Samba and remote CIFS servers. It’s pretty straightforward to handle them from Linux command line:
net rpc service --help
Usage:
net rpc service list
View configured Win32 services
net rpc service start
Start a service
net rpc service stop
Stop a service
net rpc service pause
Pause a service
net rpc service resume
Resume a service
net rpc service status
View current status of a service
net rpc service delete
Deletes a service
net rpc service create
Creates a service
Today we are going to create a PHP wrapper for this tool. Our NetService library will have two classes: One Parser and one Service class.
The Parser’s responsibility will be create the command line instruction. I will use Behat in the developing process of Parser class. Here we can see the feature file:
Feature: command line parser
Scenario: net service list
Given windows server host called "windowshost.com"
And credentials are "myDomanin/user%password"
And action is "list"
Then command line is "net rpc service list -S windowshost.com -U myDomanin/user%password"
Scenario: net service start
Given windows server host called "windowshost.com"
And service name called "ServiceName"
And credentials are "myDomanin/user%password"
And action is "start"
Then command line is "net rpc service start ServiceName -S windowshost.com -U myDomanin/user%password"
Scenario: net service stop
Given windows server host called "windowshost.com"
And service name called "ServiceName"
And credentials are "myDomanin/user%password"
And action is "stop"
Then command line is "net rpc service stop ServiceName -S windowshost.com -U myDomanin/user%password"
Scenario: net service pause
Given windows server host called "windowshost.com"
And service name called "ServiceName"
And credentials are "myDomanin/user%password"
And action is "pause"
Then command line is "net rpc service pause ServiceName -S windowshost.com -U myDomanin/user%password"
Scenario: net service resume
Given windows server host called "windowshost.com"
And service name called "ServiceName"
And credentials are "myDomanin/user%password"
And action is "resume"
Then command line is "net rpc service resume ServiceName -S windowshost.com -U myDomanin/user%password"
Scenario: net service status
Given windows server host called "windowshost.com"
And service name called "ServiceName"
And credentials are "myDomanin/user%password"
And action is "status"
Then command line is "net rpc service status ServiceName -S windowshost.com -U myDomanin/user%password"
The implementation of the feature file:
namespace NetService;
class Parser
{
private $host;
private $credentials;
public function __construct($host, $credentials)
{
$this->host = $host;
$this->credentials = $credentials;
}
public function getCommandLineForAction($action, $service = NULL)
{
if (!is_null($service)) $service = " {$service}";
return "net rpc service {$action}{$service} -S {$this->host} -U {$this->credentials}";
}
}
and finally our Service class:
namespace NetService;
use Symfony\Component\Process\Process,
NetService\Parser;
class Service
{
private $parser;
private $timeout;
const START = 'start';
const STOP = 'stop';
const STATUS = 'status';
const LIST_SERVICES = 'list';
const PAUSE = 'pause';
const RESUME = 'resume';
const DEFAULT_TIMEOUT = 3600;
public function __construct(Parser $parser)
{
$this->parser = $parser;
$this->timeout = self::DEFAULT_TIMEOUT;
}
public function start($service)
{
return $this->runProcess($this->parser->getCommandLineForAction(self::START, $service));
}
public function stop($service)
{
return $this->runProcess($this->parser->getCommandLineForAction(self::STOP, $service));
}
public function pause($service)
{
return $this->runProcess($this->parser->getCommandLineForAction(self::PAUSE, $service));
}
public function resume($service)
{
return $this->runProcess($this->parser->getCommandLineForAction(self::RESUME, $service));
}
public function status($service)
{
return $this->runProcess($this->parser->getCommandLineForAction(self::STATUS, $service));
}
public function listServices()
{
return $this->runProcess($this->parser->getCommandLineForAction(self::LIST_SERVICES));
}
public function isRunning($service)
{
$status = explode("\n", $this->status($service));
if (isset($status[0]) && strpos(strtolower($status[0]), "running") !== FALSE) {
return TRUE;
} else {
return FALSE;
}
}
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
private function runProcess($commandLine)
{
$process = new Process($commandLine);
$process->setTimeout($this->timeout);
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException($process->getErrorOutput());
}
return $process->getOutput();
}
private function parseStatus($status)
{
return explode("\n", $status);
}
}
And that’s all. Now a couple of examples:
include __DIR__ . '/../vendor/autoload.php';
use NetService\Service,
NetService\Parser;
$host = 'windowshost.com';
$serviceName = 'ServiceName';
$credentials = '{domain}/{user}%{password}';
$service = new Service(new Parser($host, $credentials));
if ($service->isRunning($serviceName)) {
echo "Service is running. Let's stop";
$service->stop($serviceName);
} else {
echo "Service isn't running. Let's start";
$service->start($serviceName);
}
//dumps status output
echo $service->status($serviceName);
include __DIR__ . '/../vendor/autoload.php';
use NetService\Service,
NetService\Parser;
$host = 'windowshost.com';
$credentials = '{domain}/{user}%{password}';
$service = new Service(new Parser($host, $credentials));
echo $service->listServices();
You can see the full code in github here. The package is also available for composer at Packaist.
Building a Silex application from one Behat/Gherkin feature file
Last days I’ve playing with Behat. Behat is a behavior driven development (BDD) framework based on Ruby’s Cucumber. Basically with Behat we defenie features within one feature file. I’m not going to crate a Behat tutorial (you can read more about Behat here). Behat use Gherkin to write the features files. When I was playing with Behat I had one idea. The idea is simple: Can we use Gherking to build a Silex application?. It was a good excuse to study Gherking, indeed
.
Here comes the feature file:
Feature: application API
Scenario: List users
Given url "/api/users/list.json"
And request method is "GET"
Then instance "\Api\Users"
And execute function "listUsers"
And format output into json
Scenario: Get user info
Given url "/api/user/{userName}.json"
And request method is "GET"
Then instance "\Api\User"
And execute function "info"
And format output into json
Scenario: Update user information
Given url "/api/user/{userName}.json"
And request method is "POST"
Then instance "\Api\User"
And execute function "update"
And format output into json
Our API use this simple library:
<?php
namespace Api;
use Symfony\Component\HttpFoundation\Request;
class User
{
private $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function info()
{
switch ($this->request->get('userName')) {
case 'gonzalo':
return array('name' => 'Gonzalo', 'surname' => 'Ayuso');
case 'peter':
return array('name' => 'Peter', 'surname' => 'Parker');
}
}
public function update()
{
return array('infoUpdated');
}
}
<?php
namespace Api;
use Symfony\Component\HttpFoundation\Request;
class Users
{
public function listUsers()
{
return array('gonzalo', 'peter');
}
}
The idea is simple. Parse the feature file with behat/gherkin component and create a silex application. And here comes the “magic”. This is a simple working prototype, just an experiment for a rainy sunday.
<?php
include __DIR__ . '/../vendor/autoload.php';
define(FEATURE_PATH, __DIR__ . '/api.feature');
use Behat\Gherkin\Lexer,
Behat\Gherkin\Parser,
Behat\Gherkin\Keywords\ArrayKeywords,
Behat\Gherkin\Node\FeatureNode,
Behat\Gherkin\Node\ScenarioNode,
Symfony\Component\HttpFoundation\Request,
Silex\Application;
$keywords = new ArrayKeywords([
'en' => [
'feature' => 'Feature',
'background' => 'Background',
'scenario' => 'Scenario',
'scenario_outline' => 'Scenario Outline',
'examples' => 'Examples',
'given' => 'Given',
'when' => 'When',
'then' => 'Then',
'and' => 'And',
'but' => 'But'
],
]);
function getMatch($subject, $pattern) {
preg_match($pattern, $subject, $matches);
return isset($matches[1]) ? $matches[1] : NULL;
}
$app = new Application();
function getScenarioConf($scenario) {
$silexConfItem = [];
/** @var $scenario ScenarioNode */
foreach ($scenario->getSteps() as $step) {
$route = getMatch($step->getText(), '/^url "([^"]*)"$/');
if (!is_null($route)) {
$silexConfItem['route'] = $route;
}
$requestMethod = getMatch($step->getText(), '/^request method is "([^"]*)"$/');
if (!is_null($requestMethod)) {
$silexConfItem['requestMethod'] = strtoupper($requestMethod);
}
$instance = getMatch($step->getText(), '/^instance "([^"]*)"$/');
if (!is_null($instance)) {
$silexConfItem['className'] = $instance;
}
$method = getMatch($step->getText(), '/^execute function "([^"]*)"$/');
if (!is_null($method)) {
$silexConfItem['method'] = $method;
}
if ($step->getText() == 'format output into json') {
$silexConfItem['jsonEncode'] = TRUE;
}
}
return $silexConfItem;
}
/** @var $features FeatureNode */
$features = (new Parser(new Lexer($keywords)))->parse(file_get_contents(FEATURE_PATH), FEATURE_PATH);
foreach ($features->getScenarios() as $scenario) {
$silexConfItem = getScenarioConf($scenario);
$app->match($silexConfItem['route'], function (Request $request) use ($app, $silexConfItem) {
function getConstructorParams($rClass, $request) {
$parameters =[];
foreach ($rClass->getMethod('__construct')->getParameters() as $parameter) {
if ('Symfony\Component\HttpFoundation\Request' == $parameter->getClass()->name) {
$parameters[$parameter->getName()] = $request;
}
}
return $parameters;
}
$rClass = new ReflectionClass($silexConfItem['className']);
$obj = ($rClass->hasMethod('__construct')) ?
$rClass->newInstanceArgs(getConstructorParams($rClass, $request)) :
new $silexConfItem['className'];
$output = $obj->{$silexConfItem['method']}();
return ($silexConfItem['jsonEncode'] === TRUE) ? $app->json($output, 200) : $output;
}
)->method($silexConfItem['requestMethod']);
}
$app->run();
You can see the source code in github. What do you think?























