Visualize the Services Graph of your Windows OS

Your Windows OS has a lot of services. You can list them in PowerShell using the command

Get-Service

You can find out how many there are using the command

Get-Service | Measure –Object

Some of these services are completely independent. Others require one or more other services and /or one or more other services depend upon them. Thus, multiple hierarchies are formed, where a service can have zero or more required services and where zero or more services depend upon it. The services with their dependencies form a structure called “graph”. A graph consists of vertices and edges. The following graph is a theoretical graph that I made and that depicts the inter-dependencies of the Windows OS services. Vertices are depicted with ovals and edges are depicted with arrows.

Example

An oval represents one service. An arrow from a service A to a service B means that service B depends upon service A. We can see that between two services there can be at most one arrow. Also, a service can depend upon zero or more services. In addition, a service can have zero or more dependent services.

In the example graph, Service 03 depends upon Service 01 and Service 02. Service 03 is required by Service 04 and Service 05. Service 03 is also required by service 06, since Service 06 depends upon Service 05.

Thus, the dependencies are transitive. Service 06 depends upon Service 01, Service 02, Service 03, and Service 05.

We also notice that not all services are interrelated among themselves; instead, independent subgraphs are formed. Service 01 to Service 06 form one isolated subgraph, Service 07 to Service 09 form another isolated subgraph, Service 10 forms a subgraph all by itself, since it is completely independent. The same goes for Service 11. Thus, in this theoretical example, we have four independent subgraphs.

A graph that has these properties is a directed, acyclic, disconnected graph.

It is a directed graph (a.k.a. digraph), because the relationships are directed. This is why we are using arrows.

It is acyclic, because loops (cycles) are not formed. If a service depends upon another service, then the opposite cannot happen. In our example, we have no loops. A loop would have been formed between Service 02, Service 03, and Service 05, had the arrow from Service 02 to Service 05 been drawn in the opposite direction. But this is something that does not happen in the case of Windows services.

It is disconnected, because isolated subgraphs are formed. There is a path from Service 03 to Service 06, but there is no path from Service 03 to Service 07 or to Service 10.

Now we know theoretically how the services of any Windows OS relate to each other. It would be nice to see this in practice. Thankfully, we can easily view the dependencies of the services. The Services snap-in is an excellent tool for this purpose. All services are listed and the properties for each service have a tab named “Dependencies”. There we can see the services (and system drivers) that the service depends upon and we can also see the services (and system drivers) that depend upon the service. Not only that, but we can view this information recursively. This means that we can see not only the dependencies, but also the dependencies of those dependencies and so on.

Is there a way to obtain this information using PowerShell? Certainly. To obtain only the first level dependencies of each service, we can use the following script, named Get-ServiceGraph.ps1:

$services = Get-Service 

foreach ($service in $services)
{ 
   $output = $service.DisplayName
   $output 

   if ($service.DependentServices)
   { 
      $output = "   The following services depend on " + $service.DisplayName
      $output

      foreach ($ds in $service.DependentServices)
      { 
         $output = "      " + $ds.DisplayName
         $output 
      }
   }

   if ($service.ServicesDependedOn)
   { 
      $output = "   The following services are required by " + $service.DisplayName
      $output

      foreach ($rs in $service.ServicesDependedOn)
      { 
         $output = "      " + $rs.DisplayName
         $output
      }
   }  
}

We can redirect the output of this script to a file, and then, we can read the file with our favorite text viewer.

To obtain the dependencies recursively, just like it is done on the “Dependencies” tab of the properties in the services snap-in, we can use the following script, named Get-ServiceGraphRecursively.ps1:

function List-DependentServices ($inputService, $inputPadding)
{
   if ($inputService.DependentServices)
   { 
      $padding = "   " + $inputPadding

      $output = $padding + "The following services depend on " + $inputService.DisplayName
      $output

      $padding = "   " + $padding

      foreach ($ds in $inputService.DependentServices)
      {
         $output = $padding + $ds.DisplayName
         $output

         List-DependentServices $ds $padding
      }
   }
}

function List-ServicesDependedOn ($inputService, $inputPadding)
{
   if ($inputService.ServicesDependedOn)
   { 
      $padding = "   " + $inputPadding

      $output = $padding + "The following services are required by " + $inputService.DisplayName
      $output

      $padding = "   " + $padding

      foreach ($rs in $inputService.ServicesDependedOn)
      {  
         $output = $padding + $rs.DisplayName
         $output

         List-ServicesDependedOn $rs $padding
      }
   } 
}

$services = Get-Service

foreach ($service in $services)
{
   $output = $service.DisplayName
   $output 

   List-DependentServices $service ""
   List-ServicesDependedOn $service ""
}

We can redirect the output of this script to a file, and then, we can read the file with our favorite text viewer.

For the two previous scripts, we can replace the DisplayName of each service with the Name of each service, if we are more comfortable with the short names of the services. Get-Service sorts the services by their Name instead of their DisplayName. Also, we can see that some of the services that are on the dependencies lists, are not originally listed by Get-Service. This is because these “phantom” services are system drivers. Get-Service does not list them, but they come up in the dependencies lists. Because we should be extra careful when working with system drivers, Get-Service does not display them. It is possible and easy to obtain a list of the system drivers, but this is beyond the scope of this post.

The last script operates recursively. I have created two recursive functions: List-DependentServices and List-ServicesDependedOn. These functions are recursive, because in their code they call themselves. This is how I am able to provide the full information that can also be obtained from the Services snap-in.

Reading the output from the previous two scripts can give us a good idea about the services graph of our Windows OS. But it would be great if we could also visualize it. This is what we are going to do for the rest of this post. Specifically, we are going to see how a few different graph viewers operate. For each graph viewer, we are going to use PowerShell to extract and transform the services graph data so that the data can be used by the particular graph viewer.

GraphViz

GraphViz is a very popular graph viewer, especially among scientists. GraphViz is free and you can find it at http://www.graphviz.org/. I find the Windows version of GraphViz to be a little buggy. Still, GraphViz is easy to learn. After you install GraphViz, you run the program gvedit.exe. Then, from gvedit.exe, you open a text file with a .gv extension that contains the information for the nodes and vertices of the graph.

The example graph in the beginning of the post would be represented in GraphViz with an Example.gv text file as follows:

digraph Example {
"Service 01" -> "Service 03"
"Service 02" -> "Service 03"
"Service 03" -> "Service 04"
"Service 03" -> "Service 05"
"Service 02" -> "Service 05"
"Service 05" -> "Service 06"
"Service 07" -> "Service 08"
"Service 08" -> "Service 09"
"Service 10"
"Service 11"
}

GraphViz depicts this graph exactly as in the image in the beginning of this post.

The following script is named Get-ServiceGraphForGraphviz.ps1. If you direct the output of this script to a text file with the extension .gv, you will be able to open the .gv file with GraphViz and view the services graph of your Windows OS.

$output = 'digraph ServiceGraphForGraphviz {'
$output

$services = Get-Service 

foreach ($service in $services)
{ 
   if ($service.DependentServices)
   { 
      foreach ($ds in $service.DependentServices)
      { 
         $output = '"' + $service.DisplayName + '"' + ' -> ' + '"' + $ds.DisplayName + '"' 
         $output
      }
   }
   else
   {
      $output = '"' + $service.DisplayName + '"'
      $output
   }                              
}

$output = '}'
$output

The following script is named Get-ServiceGraphForGraphvizDependenciesOnly.ps1. If you direct its output to a file with the extension .gv, you will be able to open the .gv with GraphViz. The difference with the previous file is that this one does not contain the nodes that are completely independent. Indeed, these nodes usually do not need to be viewed in order for someone to understand the dependencies between the other services.

$output = 'digraph ServiceGraphForGraphvizDependenciesOnly {'
$output

$services = Get-Service 

foreach ($service in $services)
{ 
   if ($service.DependentServices)
   { 
      foreach ($ds in $service.DependentServices)
      { 
         $output = '"' + $service.DisplayName + '"' + ' -> ' + '"' + $ds.DisplayName + '"' 
         $output
      }
   }                         
}

$output = '}'
$output

A limitation of GraphViz is that it does not let you move the nodes in the graph. Moving the nodes around sometimes helps in better understanding the graph. Still, GraphViz lets you change the layout engine, if the layout does not satisfy you.

NodeXL Excel Template

NodeXL is a template for Excel, that helps in the creation and visualization of graphs. NodeXL is donationware and you can find it at http://nodexl.codeplex.com/. NodeXL lets you move the nodes of your graph and rearrange them in order to help you get a better “feel” and understanding for it. It also lets you easily zoom into and out from different areas of your graph.

After you setup NodeXL, you will find the NodeXL Excel Template right above your start menu. If you want to find the actual template file, its name is NodeXLGraph.xltx. Open it in Excel and a new workbook will be created. The first thing you have to do is to go to the ribbon, to the NodeXL tab and change the type of graph to “Directed”.

Directed

Then in the “Edges” sheet, you should put the beginning and end service for each dependency. Refreshing the graph will automatically create the corresponding vertices in the “Vertices” sheet. If there are any services that have no dependencies whatsoever, but you still want to display them, you should add them to the “Vertices” sheet. You should also explicitly set the Visibility cell to “Show” for those.

For the example graph, the Edges sheet will be as follows:

Edges

In the Vertex1 column we put the “parent” service. In the Vertex2 column, we put the “child” (dependent) service. I set the width to a subjective value.

The Vertices sheet will be as follows:

Vertices

The Vertex column was populated automatically from the first two columns of the Edges sheet. I added the last two lines, since these correspond to services that have no dependencies. I also had to set the Visibility for these two rows explicitly, in order for them to appear in the graph. I also copied the values of the Vertex column to the Label column, in order for each service’s name to appear in the graph. I also set the Color, Shape, and Label Fill Color columns to subjective values.

The corresponding graph, after I rearranged the vertices with the mouse, is as follows:

Graph

The PowerShell script that will provide the values for the first two columns of the Edges sheet is named Get-ServiceGraphForNodeXLDependenciesOnly.ps1. If you redirect the output of this script to a text file, it will provide two columns separated by tabs. The data can easily be copied in Excel.

$services = Get-Service 

foreach ($service in $services)
{ 
   if ($service.DependentServices)
   { 
      foreach ($ds in $service.DependentServices)
      { 
         $output = $service.DisplayName + "`t" + $ds.DisplayName
         $output 
      }
   }                           
}

If you want to add the independent services to the graph, the following script will provide their display names. It is named Get-ServiceGraphForNodeXLIndependentServices.ps1.

$services = Get-Service 

foreach ($service in $services)
{ 
   if (! $service.DependentServices)
   { 
      $output = $service.DisplayName
      $output 
   }                       
}

You can redirect the output of this script to a file and then copy its contents after the last value of the first column of the Vertices sheet. Remember to set the visibility explicitly to “Show” for these rows.

Lastly, if you just want to view everything (just data with no graph visualization) in a regular Excel sheet, you can redirect the output of the following script to a file and open it in Excel. The script is named Get-ServiceGraphForExcel.ps1:

$services = Get-Service 

foreach ($service in $services)
{ 
   if ($service.DependentServices)
   { 
      foreach ($ds in $service.DependentServices)
      { 
         $output = $service.DisplayName + "`t" + $ds.DisplayName
         $output 
      }
   }
   else
   {
      $output = $service.DisplayName
      $output 
   }                           
}

The services that have no dependencies occupy the first column only, whereas a service and its dependent service occupy the first and second column and are separated by a tab.

Neo4j

Neo4j is a graph database. You can find Neo4j at http://www.neo4j.org/. Neo4j is free for personal use.

A graph database lets you input vertices (Neo4j calls them nodes) and edges (Neo4j calls them relationships). It then lets you query the resulting graph. It can produce the nodes, the relationships, the relationships of a specific node one or more hops away, the full spectrum of nodes and relationships between two nodes (thus solving the so-called “Kevin Bacon problem”), and the shortest paths between two nodes. Thus, a graph database knows how to traverse your graph for you.

Neo4j excels as a graph database. And it has an excellent built-in viewer for your graph. Let us see how Neo4j will handle our small example graph.

You can use Neo4j by using your browser to navigate to http://localhost:7474. The dollar sign is the place where you can write or copy (use CTRL-V) one or more commands. There is an triangle/arrow next to the dollar sign that executes what you have pasted.

The command

MATCH (n) RETURN n

shows everything in the graph database.

The command

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

deletes everything in the graph database. The same result can be achieved with the following two commands:

MATCH (n)-[r]-() DELETE r
MATCH (n) DELETE n

The first command deletes all relationships and the second command deletes all nodes. You have to run each one of these commands by itself. You cannot run them as part of the same execution. And you first have to run the first command, because all relationships of a node have to be deleted before that node can be deleted.

After you have made sure that your database contains nothing, you can run the following commands as part of one execution operation. You can copy them (as text) and paste them with CTRL-V to the dollar sign input prompt.

CREATE (Service_01:Service {displayName: "Service 01"})
CREATE (Service_02:Service {displayName: "Service 02"})
CREATE (Service_03:Service {displayName: "Service 03"})
CREATE (Service_04:Service {displayName: "Service 04"})
CREATE (Service_05:Service {displayName: "Service 05"})
CREATE (Service_06:Service {displayName: "Service 06"})
CREATE (Service_07:Service {displayName: "Service 07"})
CREATE (Service_08:Service {displayName: "Service 08"})
CREATE (Service_09:Service {displayName: "Service 09"})
CREATE (Service_10:Service {displayName: "Service 10"})
CREATE (Service_11:Service {displayName: "Service 11"})
CREATE (Service_01)-[:IS_REQUIRED_BY]->(Service_03)
CREATE (Service_02)-[:IS_REQUIRED_BY]->(Service_03)
CREATE (Service_03)-[:IS_REQUIRED_BY]->(Service_04)
CREATE (Service_03)-[:IS_REQUIRED_BY]->(Service_05)
CREATE (Service_02)-[:IS_REQUIRED_BY]->(Service_05)
CREATE (Service_05)-[:IS_REQUIRED_BY]->(Service_06)
CREATE (Service_07)-[:IS_REQUIRED_BY]->(Service_08)
CREATE (Service_08)-[:IS_REQUIRED_BY]->(Service_09)

Now your graph has been inserted into Neo4j. Please note that you must execute these commands only once. If you execute them a second time, new nodes and relationships will be created. And so on. So, if you need to re-execute these commands, be sure to delete everything from the database first.

Use the command:

MATCH (n) RETURN n

to see everything in your graph. The following command does exactly the same:

MATCH (s) RETURN s

This is because it actually is the same command. All we did was change the variable’s name from n to s. In my mind, n stands for “node” and s stands for “service”.

Immediately, you will notice that Neo4j displays its internal number that it allocates for each node. It is each to change that and display the displayName of each node. In the graph that Neo4j returns, double click one of the nodes. A little popup window appears with details for the node. There are two tabs in that popup window. One is named “Properties”. The tab next to it is named “Style”, but you can only see its icon, that looks like an eye.

Properties

Click the icon that looks like an eye and the style tab will appear. Here you can specify that Neo4j will display the displayName (instead of Neo4j’s internal ID) for each node. As you can see, Neo4j starts its internal ID from zero.

Style

The two previous commands display the whole graph. After I rearranged the nodes with my mouse, here is what the graph looked like:

ExampleGraphNeo4j

The following command returns all nodes that have relationships (and their relationships). Thus, Service 10 and Service 11 are excluded. Please note that the pattern ()-[]-() means node-relationship-node without having to specify the direction of the relationship.

MATCH (s)-[]-() RETURN s

The following command returns all relationships (and the corresponding nodes). Thus, its output graph is the same as the previous command.

MATCH ()-[r]-() RETURN r

The following command returns all immediate relationships of the Service 03.

MATCH (Service_03:Service {displayName: "Service 03"})-[r]-() RETURN r

The following command returns everything that has a relationship with Service 01, no matter how many hops away this is. Thus, this command solves the “Kevin Bacon problem”. The asterisk denotes that the graph will be traversed up to infinite hops. If we wanted to traverse the graph up to, say, 6 hops (degrees of separation), then in place of the asterisk we would write *1..6.

MATCH (Service_01:Service {displayName: "Service 01"})-[*]-(s) RETURN Service_01, s

The following command returns the shortest paths between two nodes.

MATCH (Service_04:Service {displayName: "Service 04"}), (Service_06:Service {displayName: "Service 06"}),
  p = allShortestPaths((Service_04)-[*]-(Service_06))
RETURN p

Neo4j has a site/subdomain at http://console.neo4j.org that you can directly do everything we did here without ever installing Neo4j. Just visit this site with your browser, press Clear DB and in the lower part of the page, delete the command that may be there, Then copy the batch of our example CREATE commands there. You will immediately get our example graph. But the viewer in the real Neo4j installation is way better. Just for comparison, here is what the graph looks like in the Neo4j site:

ExampleGraphNeo4jConsole

We are now ready to extract, transform and load our real data. Keep in mind that you first have to remove everything from the database first, if you do not want the example data to coexist with the real data.

So, use the command :

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

to delete everything in the graph database. Alternatively, as I already mentioned, you can use the following two commands

MATCH (n)-[r]-() DELETE r
MATCH (n) DELETE n

one by one and in that order.

You can check that database has been cleared by running the command

MATCH (n) RETURN n

The following PowerShell script is called Get-ServiceGraphForNeo4j.ps1 and creates the CREATE commands that we need in order for our Windows OS services and their relationships to be inserted into Neo4j. Just redirect the output of this script to a text file, then copy the contents of the file to the Neo4j dollar sign command prompt, and then press the execute triangle/arrow.

function Sanitize ($inputString)
{
   $outputString = $inputString

   $outputString = $outputString.Replace(' ','_')
   $outputString = $outputString.Replace('.','_')
   $outputString = $outputString.Replace(',','_')
   $outputString = $outputString.Replace('+','_')
   $outputString = $outputString.Replace('-','_')
   $outputString = $outputString.Replace('(','_')
   $outputString = $outputString.Replace(')','_')
   $outputString = $outputString.Replace('/','_')

   return $outputString
}

$services = Get-Service 

foreach ($service in $services)
{ 
   $s = Sanitize($service.DisplayName)

   $output = 'CREATE (' + $s + ':Service {displayName: ' + '"' + $service.DisplayName + '"' + '})'
   $output 
}

foreach ($service in $services)
{ 
   if ($service.DependentServices)
   { 
      $s1 = Sanitize($service.DisplayName)

      foreach ($ds in $service.DependentServices)
      { 

         $s2 = Sanitize($ds.DisplayName) 

         $output = 'CREATE (' + $s1 + ')-[:IS_REQUIRED_BY]->(' + $s2 + ')' 
         $output 
      }
   }
}

For the CREATE command of a node, Neo4j needs us to provide a “name id” for the node. Valid characters for this name are letters, numbers and underscores. Spaces are illegal, dots also, as well as lots of other characters. One would think that we could use the service’s name (instead of its display name) for this name id. Unfortunately, there are services that in their name have spaces and other characters that are inappropriate for a Neo4j name id.

To overcome this problem, I chose to use each service’s display name, but replace the unwanted (by Neo4j) characters with underscores. The Sanitize function does this work. If you find more unwanted characters in your services, add appropriate lines in the Sanitize function. Or, you could change the Sanitize function to replace each function’s name or display name with a hash. This is a very useful and scientific approach. Or you could come up with another solution. Feel free to think and implement.

Continuously finding “illegal” characters and replacing them all with underscores is not a very useful approach and an error prone one. This is because a service with the display name of “Test.0.1” and a service with the display name of “Test 0 1” will end up having the same name id of “Test_0_1” .

But, by following this method both for service names and for service display names, I learned a lot about them. For example, one would think that a service’s name (as opposed to the service’s display name) would be short and would contain no spaces. Instead, there is a service from F-Secure with the following properties:

Service name: F-Secure Gatekeeper Handler Starter
Display name: FSGKHS
Description:  FSGKHS

I think things are a little backwards here. What should have been the service name is the display name and vice versa. And the description… OK, you get it.

So, there are service names with characters that Neo4j would not like for a name id. And the same holds true for service display names. There are spaces to separate words, dots to separate version and subversion numbers , + for COM+, – for F-Secure, parentheses from Google services, / for TCP/IP, and who knows what else.

So, either implement a strategy that will help you create sane identifiers for Neo4j, or just experiment and see what characters Neo4j “spits out”. Then, you can add them in the Sanitize function.

Epilogue

The PowerShell scripts I provide in this post can be run with every version of PowerShell, even PowerShell 1.0. In addition, the different software that I used can be installed even as far back as Windows XP. Yes, GraphViz, NodeXL,and Neo4j can be installed even on downlevel clients as far back as Windows XP.

For graph viewing, I chose three of the many software programs for graphs. I chose these three because they are well known, I like them and they are inexpensive or free. There are other programs just as good and just as free and inexpensive. Feel free to use whichever you like. If you can afford it, you may also want to look for more expensive programs. Microsoft Automatic Graph Layout (MSAGL) and Microsoft Visio are two of those.

There is also a huge amount of JavaScript libraries that can help in the creation of graphs, but this is really a solution for JavaScript developers, not administrators. Still, if you want to go down that road, I recommend the JavaScript InfoVis Toolkit at http://philogb.github.io/jit/ and the awesome Data-Driven Documents at http://d3js.org/. With JavaScript, you have the capability to create highly interactive graphs. For example, you can create a graph that lets you click on each node (service) and change its color (denoting the status of the service) to green (running) or red (stopped). Then you can program the graph to cascade the change to subordinate nodes. This way, you can have a nice what if analysis for the times you are planning to stop a service.

All this talk about graphs may be useful, but even if you never perform such an analysis, you can get by just by using the Services MMC snap-in. It can give you all the information you may need. If you are planning to study a service for whatever reason (i.e. you may plan to disable it and worry about the impact) all you need to know are the service’s dependencies. The Services MMC snap-in can give you recursively all the services that depend on a particular service and all the services that the particular service depends upon. The same goes for my “recursive” script that I presented at the beginning of the post. This is all the information you may ever need concerning the hierarchy of services (the services graph) of your Windows OS.

Still, it is nice to able to see the big picture. You get a better feel for the services graph overall, if you spend some time analyzing that graph. So, I guess, the most important lesson from this post is not how to view the services graph per se, but that PowerShell is a great tool that can help in the extraction of data from a Windows OS and in the subsequent transformation of that data to the format that your analysis tool needs.

Advertisements

About Dimitrios Kalemis

I am a systems engineer specializing in Microsoft products and technologies. I am also an author. Please visit my blog to see the blog posts I have written, the books I have written and the applications I have created. I definitely recommend my blog posts under the category "Management", all my books and all my applications. I believe that you will find them interesting and useful. I am in the process of writing more blog posts and books, so please visit my blog from time to time to see what I come up with next. I am also active on other sites; links to those you can find in the "About me" page of my blog.
This entry was posted in Administration. Bookmark the permalink.