Update: I have released an installed, configuration instruction, as well as source code for an application similar to the one demonstrated in this blog. Find the information at the folloing site - http://blogs.technet.com/b/neilp/archive/2014/09/10/cmaae.aspx 


Introduction -

During my last blog post I detailed some of the internal components of the System Center 2012 Application Request and Approval system. Included was a look into the views created in the CM Database, WMI specific data and methods, and finally I’ve shown how to approve or deny an application request using PowerShell. In this post I will be walking through a sample end-to-end application request notification and approval system. This solution is only a sample of what is possible. This blog post is meant to generate ideas and provide sample code that may help fulfill those ideas. There may be items included in this solution that would not work well in your environment. However as you will see, the sample Runbooks are easily modifiable and can be used as a starting point and can be crafted into just about any environment.

The sample solution detailed in this post consists of three Orchestrator Runbooks, a simple custom ASP.NET application, and many of the elements (PowerShell and Data) previously discussed. The goal of this solution is quite simply to provide an email alert to the appropriate approver each time a new application request has been entered into the Configuration Manager system. With this email, the approver will have the ability to approve or deny the application request without having access or the need to access the Configuration Manager console.

All Reference Material for this Blog (Orchestrator Runbook Exports) can be found at the following location – Download. 

Solution High Level Overview –

  • An Orchestrator Runbooks monitors the application approval system (WMI) checking for new requests every two minutes.
  • When a new request is detected, information about the request including the Request UID is placed into an email. Included in this email is a link to the ASP.NET application page on which the application can be approved or denied. This email is sent to the requesting users Manager as defined in Active Directory.
  • Once received, the approver can follow the embedded URL to the approval tool. In this tool the application request can be approved or denied.
  • Once the application has been approved or denied, an email is generated and sent to the requesting user with the approval/denial details.
  • Additionally there is workflow in the solution that will send a follow up email in the event an application request has not been acted upon in a configurable amount days. The solution will also auto deny any request that has not been acted upon in a configurable amount of days.

As you can see, this is a very simple solution in terms of what is being performed, however very nicely builds onto the new Configuration Manager Application Approval System. Without a solution such as this, Configuration Manager provides no email notification of pending application approvals and likewise requires access to the console for all approval activity. 

Solution In Action –

Before digging into the Runbooks and application code, let me show the full end-to-end execution of this solution. This will provide context as we look at the Orchestrator Runbooks, scripts, and .NET code.

Client Side logged in as user –

User navigates to the application portal and selects an application to request.

 

 Comments are added to the request and the request is submitted.

On the Application Approvers side –

An email alert is auto generated and sent to the requesting user’s manager –

 

The manager can then follow the embedded URL to the approval application. From this application the approver can approve or deny the request and also add a comment.

 

Back on the requesting user's end an email will be received notifying of the approval decision. If the application has been approved the installation will begin momentarily .

 

Additionally if the application request has not been acted upon in a specified number of days a second email (not shown) will be sent to the requesting user’s manager. This email is almost identical to the first however indicates that the request is ageing. Finally if the application request is not acted upon after a configurable amount of days, the request will be auto denied due to inactivity and an email will be sent to the user indicating this (also not shown).

 

Sample Solution Deep Dive –

Components –

  • Data Manipulation and Active Directory Integration Packs (thes must deployed to the Runbook Designer before importing the Runbook samples).
  • Application Request Gather Runbook.
  • Application Request Action Runbook.
  • Get Email Address Runbook.
  • Approval ASP Application.

Application Request Gather Runbook -

Monitor Date/Time – is configured to execute this Runbook every two minutes.

Get All Application Requests – PowerShell that gathers each application request with a CurrentState of 1 (See Previous Blog for explination of Current State), publishes the Requests GUID.

 $a=@()

$app = Get-WmiObject -Class SMS_UserApplicationRequest -Namespace root/SMS/site_<Published Data Site Code > -ComputerName <Published Data Computer Name> | where-object -FilterScript {$_.CurrentState -eq "1"}

foreach ($appr in $app)

{

$a += $appr.RequestGuid

}

Gather Data for Each Request – PowerShell that loops through each application request (CurrentState = 1) by Request ID and pulls back information to be placed into the email and also does math on the last modified date in order to determine the next move (First Email, Second Email, Cancel, etc.).

$app = Get-WmiObject -Class SMS_UserApplicationRequest -Namespace root/SMS/site_<Published Data Site Code> -ComputerName <Published Data Site Server> | where-object -FilterScript {$_.RequestGuid -eq “<Published Data Request UID>”}

foreach ($appr in $app) {$comment = $appr.Comments; $user = $appr.User; $appName = $appr.Application; $date = $appr.ConvertToDateTime($appr.LastModifiedDate)}

$today = get-date

$diffd = ($today - $date).days

$diffh = ($today - $date).hours

$diffm = ($today - $date).minutes

if ($diffd -gt 2)

{

$action = "Cancel"

}

if ($diffd -lt 1 -and $diffh -lt 1 -and $diffm -lt 2)

{

$action = "FirstEmail"

}

if ($diffd -eq 1 -and $diffh -gt 1 -and $diffm -lt 2 )

{

$action = "SecondEmail"

}

Get Email Address 1 and 2 – This calls a Runbook that will run a series of data manipulation and Get AD User lookups against the requesting user name. The outcome is an email address for both the requesting user and the user’s manager. I will not detail this Runbook in this blog posting, but the Runbook is provided at the download link.

First Email and Second Email – These two activities send out a very basic email. The content of these emails is made of data from the previous activity. Included in this email is a URL to the application request approve / deny application. This URL is made up of the name of the website plus a parameter of the Application Request UID (Notice the ?UID=) . We will discuss how this application works and the significance of the Request UID later in this post.

Email Body section of the send email activities.

Auto Deny the Application Request – this activity calls the Application Request Action Runbook (See next group for an explanation).

 

Application Request Action Runbook –

Initialize Data – consumes the Request UID, The approval or denial decision, and comments from the ASP.Net Application or from the Request Gather Runbook (in the event of an auto deny).

Approve / Deny – this activity triggers the WMI method to approve or deny each application request. This script was detailed in my last blog posting.

$wmi = Get-WmiObject -Class sms_userapplicationrequest -Filter 'requestguid = "<Published Data Request UID>"' -ComputerName "<Published Data Site Server>" -Namespace "root\sms\site_<Published Data Site Code>"

if ($wmi)

{

$rtn = $wmi.<Published Data Approve/Deny>(‘<Published Data Description>’);

}

Gather Data for Each Request – this is more or less similar to the gather activity of the same name in the first Runbook. Here we are re-gathering the data for the end user Approve/Deny email. I will not paste the code in here, refer back to the last section or download the sample Runbooks and inspect there for more information.

Get Email Address – Gets email address for both requesting user and users manager.

Send Email – Finally once the application has been approved or Denied an email is sent to the requesting user with request status.

 

Approval ASP.NET Application -

The final piece to this sample solution is the ASP.NET application. There is nothing fancy happening with this application. It quite simply performs the following tasks

  • Consumes the Request UID from the passed URL (recall this was constructed from the Orchestrator Send Email Activity).
  • Queries WMI on the site server for information about the request and displays this on the Web APP.
  • Takes the Approved/Denied value and the entered comments, executes the Application Request Action Runbook, passing to it these two values as parameters.

So putting it even more simply, the application is basically a ‘fancy’ Runbook interface.  This application could easily be replaced with a script, SharePoint, or many other types of interfaces. The value in this form is that it is dynamic (based on the Request UID). Because of this we do not have to store any additional data anywhere. If you think through the process, between the time a user requests access to an application and the time it takes an approval administrator to actually perform the approval, the request is in an idle state. How do we store the request in a format that can be accessible at any time? In this solution it is stored in the form of a URL embedded inside of an email and a dynamic application that consumes the Request ID from the embedded URL.

I will not be providing an installer for this application at this time, however here is some sample code. If there is interest in having an installer package please let me know. Given enough interest I will complete a few remaining items and make it available for download.

App Approval Code
  1. using System;
  2. using System.Management;
  3. using System.Text;
  4. using AppApproval.SCOService;
  5. using System.Data.Services.Client;
  6. using System.Linq;
  7. using System.Configuration;
  8.  
  9. namespace AppApproval
  10. {
  11.     public partial class _Default : System.Web.UI.Page
  12.     {
  13.  
  14.         string strUser;
  15.         string strAppR;
  16.         string strAppC;
  17.         
  18.         string sSiteServer = ConfigurationManager.AppSettings["SiteSever"];
  19.         string sSiteCode = ConfigurationManager.AppSettings["SiteCode"];
  20.         string sSCORCH = ConfigurationManager.AppSettings["SCORCHWS"];
  21.         string sRBUID = ConfigurationManager.AppSettings["RBUID"];
  22.  
  23.         string sUID;
  24.      
  25.         protected void Page_Load(object sender, EventArgs e)
  26.         {
  27.  
  28.             Uri MyUrl = Request.Url;
  29.             string URL = MyUrl.AbsoluteUri.ToString();
  30.             sUID = Request.QueryString.Get("UID");
  31.  
  32.             ConnectionOptions options = new ConnectionOptions();
  33.  
  34.             ManagementScope scope = new ManagementScope("\\\\" + sSiteServer + "\\root\\sms\\Site_" + sSiteCode);
  35.             
  36.             scope.Connect();
  37.  
  38.             ObjectQuery query = new ObjectQuery("SELECT * FROM SMS_UserApplicationRequest WHERE RequestGuid ='" + sUID + "'");
  39.             ManagementObjectSearcher search = new ManagementObjectSearcher(scope, query);
  40.  
  41.             ManagementObjectCollection queryCollection = search.Get();
  42.             
  43.             foreach (ManagementObject User in search.Get())
  44.             {
  45.                 strUser = User["User"].ToString();
  46.                 strAppR = User["Application"].ToString();
  47.                 strAppC = User["Comments"].ToString();
  48.             }
  49.  
  50.             lblUser.Text = strUser;
  51.             lblApplication.Text = strAppR;
  52.             lblComment.Text = strAppC;
  53.  
  54.         }
  55.  
  56.         protected void btnSubmit_Click(object sender, EventArgs e)
  57.         {
  58.             string approve = ddlApprove.SelectedItem.ToString();
  59.  
  60.             Guid runbookId = new Guid(sRBUID);
  61.             string serviceRoot = sSCORCH;
  62.  
  63.             SCOService.OrchestratorContext context = new SCOService.OrchestratorContext(new Uri(serviceRoot));
  64.  
  65.             context.Credentials = System.Net.CredentialCache.DefaultCredentials;
  66.  
  67.             var runbookParams = context.RunbookParameters.Where(runbookParam => runbookParam.RunbookId == runbookId && runbookParam.Direction == "In");
  68.  
  69.             // Configure the XML for the parameters
  70.             StringBuilder parametersXml = new StringBuilder();
  71.             if (runbookParams != null && runbookParams.Count() > 0)
  72.             {
  73.  
  74.                 parametersXml.Append("<Data>");
  75.                 foreach (var param in runbookParams)
  76.                 {
  77.                     if (param.Name == "RUID:")
  78.                     {
  79.                         parametersXml.AppendFormat("<Parameter><ID>{0}</ID><Value>{1}</Value></Parameter>", param.Id.ToString("B"), sUID);
  80.                     }
  81.  
  82.                     if (param.Name == "Approve/Deny:")
  83.                     {
  84.                         parametersXml.AppendFormat("<Parameter><ID>{0}</ID><Value>{1}</Value></Parameter>", param.Id.ToString("B"), approve);
  85.                     }
  86.  
  87.                     if (param.Name == "Description:")
  88.                     {
  89.                         parametersXml.AppendFormat("<Parameter><ID>{0}</ID><Value>{1}</Value></Parameter>", param.Id.ToString("B"), txtComments.Text);
  90.                     }
  91.                   
  92.                 }
  93.                 parametersXml.Append("</Data>");
  94.             }
  95.  
  96.             try
  97.             {
  98.                 // Create new job and assign runbook Id and parameters.
  99.                 Job job = new Job();
  100.                 job.RunbookId = runbookId;
  101.                 job.Parameters = parametersXml.ToString();
  102.  
  103.                 // Add newly created job.
  104.                 context.AddToJobs(job);
  105.                 context.SaveChanges();
  106.  
  107.             }
  108.  
  109.             catch (DataServiceQueryException ex)
  110.             {
  111.                 throw new ApplicationException("Error starting runbook.", ex);
  112.             }
  113.             
  114.         }
  115.  
  116.         protected void ddlApprove_SelectedIndexChanged(object sender, EventArgs e)
  117.         {
  118.  
  119.         }
  120.  
  121.     }
  122. }

Additionally the following has been added to the <appSettings> section of Web.config file.

Web.config File
  1. <appSettings>
  2.   <addkey="SiteSever"value="Site Server" />
  3.   <addkey="SiteCode"value="Site Code" />
  4.   <addkey="SCORCHWS"value="SCORCH Web Service URL" />
  5.   <addkey="RBUID"value="UID of the Application Request Action Runbook" />
  6.   </appSettings>


Conclusion -

So there we have it, my own custom application request notification and approval add on solution for Configuration Manager. The benefit to a solution such as this is that no longer will there be a need for manual investigation into the current list of applications requiring approval, nor will access to the console be required for the approval to take place. I hope that this post has provided you with an idea on how you would accomplish something similar were you to have the need.

As a reminder all Runbooks detailed in this post can be found on my connect site – Download Site.