Hi everyone!
My Name is Fabian Sick, Cloud Solution Architect at Microsoft Germany for Microsoft Endpoint Manager related topics. At nearly all my previous engagements we came to the same point facing one question:
How can we improve the Software Deployment Process?
The most IMAC (Install Move Add Change) Software Order processes require a first and second level customer contact and therefore consume a lot of time and money as well. Therefore, we started our journey to find a new solution to simplify this whole process handled by the end user – away from time and money consuming processes.
We defined our possibilities and dependencies and identified Microsoft Teams as the ‘place to be’ for our end users, who start their day within the Teams Platform (including Chat, Telephony, Meetings, Viva, Company Communicator, Channels etc.) Instead of creating another web interface/GUI/tool we planned to include the new application within that already very familiar platform.
Once the location has been defined the dependencies for our Solution had to be clarified:
- Software Approval Units
- On-Prem Client Management (MECM/Configuration Manager)
- Connector for Cloud based Client Management (MEM/Intune)
It was necessary to ensure that client based licensed software still needs to be approved by the responsible units and that future concepts are still manageable within our solution. The final solution was a conglomerate of the following tools:
- Dataverse
- As software catalog
- PowerApp
- As user interface
- PowerAutomate
- Starts the approval process
- Approvals
- Teams App which is already available
- Approves/Rejects ordered software
- Azure Automation Hybrid Runbook Worker
- Connector between cloud (modern) and on-prem (legacy) technology
- Microsoft Endpoint Configuration Manager
- Established Client Management solution
The outcome, a simple Low-Code/No-Code solution based on Powerplatform that is built in less than a day is more than satisfying and exactly what we were looking for.
But before we start, I would like to address some personal words.
A project like this wouldn’t be possible without a great team. Of all the people I am grateful to,
I would like to highlight Thomas Hantsch, Erkan Grueschow, Sandrine Manel, Christian Puetz, Stefan Wentingmann, Chris Niebuhr and Robin Mueller – without these great colleagues this would never have been possible.
Dataverse | Software catalog
Like everything in IT, it starts with the correct collection of data. For a complete software catalog solution there might be subjects like vendor, owner, language etc. required, but for this application the following attributes were enough:
- ApplicationName
- Owner
- Approval
- Approver 1st
- Approver 2nd
- CollectionID
With this kind of data source I had everything which is needed to start with the PowerApp.
PowerApp
Due to the fact that the PowerApp itself is published in Microsoft Teams I didn’t want any kind of landing page which would just annoy the user after the second or third time they use it.
Therefore, I just show the user a collection based on my Dataverse table, one text bar to search a specific product, another one to enter the device name and a third to enter a reason if the software requires an approval.
The Logic is quite simple:
The User selects a software product and enters a device name to enable the order button. In addition, if an selected application needs an approval, the text field for the justification gets enabled and the User has to enter a valid reason to enable the order button.
This button will start the PowerAutomate flow with specified information.
You can find some ‘Code’ examples at the end of the blog.
PowerAutomate
By hitting the ‘Order’ button we start the PowerAutomate flow. We will start one PowerAutomate flow for every item which is selected:
We select device name, the full name of the user, the E-Mail of the user, DataverseID and the reason (optional) and use this information as trigger at the Flow:
The next step is to get the related information out of the Dataverse. This will be realized by ‘Get a row by ID’:
Now we got all related information to our selected software product. This Information will now be sent to a dedicated Teams Channel:
Now the first of our three PowerShell Scripts will take over and evaluate the data provided by the user (more information will be provided at the section ‘Azure Automation’):
This Powershell Script will check if the client exists at the environment. If not, the user will be informed by Flow Bot the entered device name does not exist:
If the device name is correct, a second Azure Runbook will start to check if the client is already part of the collection. After this check is completed the final approval check starts:
So according to our Dataverse table we will check if an approval is needed. If yes, the first and second approver will get a request via Approvals app and mail:
Once this is approved, the client will be added to the Install collection using the third PowerShell Script.
Azure Automation Hybrid Runbook Worker
If you want to know more about Azure Automation Hybrid Runbook Worker please follow this Link: Azure Automation Hybrid Runbook Worker overview | Microsoft Docs but for our use case it’s good to understand that this enables you to run Azure Runbooks local on your on-prem server.
All you need is:
- Log Analytics Workspace
- Azure Automation Account
- Hybrid Worker Group
- Microsoft Monitoring Agent (installed on your server)
- Run the following Powershell:
New-OnPremiseHybridWorker.ps1 -WorkspaceName $WorkspaceName -AutomationAccountName $AutomationAccount -SubscriptionID $SubscriptionID -HybridGroupName $HybridWorkerGroup -ResourceGroupName $ResourceGroup
For this workflow I added three different runbooks (PowerShell code is added to the End) to my Hybrid Worker Account:
- Check if Client Exists (PA_SoftwareOrder_CheckClient)
- Check if Direct Rule Exists (PA_SoftwareOrder_CheckCollection)
- Place the Order (PA_SoftwareOrder_Order)
Once the workflow is completed, the Built-In Mechanism of MECM will take over and deploy the software to the client.
Summary
I would like to show you Step by Step what’s happening when we order Software:
The User requests the software at the PowerApp pinned in Teams
At the dedicated IT Department Microsoft Teams Channel a new post will be generated (just an example of documentation – you could also generate a Ticket via REST API for Documentation, send a mail or generate a new Row at a defined Dataverse ‘Softwareorders’)
The Runbook will work in sequence defined tasks parallel for every Product we have added to our order.
Approval Flow will be triggered to all defined Approvers
After the Request is Approved the Requestor gets a Teams Message
Complete Flow Time (it depends how long the Approver needs to approve the Request)
The Client will be added to the Config Manager Software Collection (I have never tested it but I am pretty sure that other Client Management tools can be addressed the same way)
After the next Scan Cycle is finished, the user will see the Software at the Software Center – if it’s a required Installation the Install process will start immediately.
And that’s it – the user will now get informed by the Toast Notifications of Config Manager and there is no need for any human resource to write a mail or make a call.
Code Snippets
PowerApp:
Start the PowerAutomate Flow:
ForAll(Selection.AllItems,SoftwareOrder.Run(DeviceName.Text,User().FullName,User().Email,ThisRecord.Softwarecatalog,Reason.Text));Clear(col_Software);Clear(MyOrder);Collect(col_Software,Softwarecatalogs);Reset(DeviceName);Reset(Reason);Reset(Productsearch)
Disable/Enable the Order Button:
If(
Or(
IsBlank(DeviceName.Text),
CountRows(MyOrder)<1,
And(
CountRows(Filter(MyOrder,Approval = 'Approval (Softwarecatalogs)'.Yes))>0,
IsBlank(Reason.Text)
)
),
DisplayMode.Disabled,
DisplayMode.Edit
)
Disable/Enable the Reason Textfield:
If(
CountRows(
Filter(MyOrder, Approval = 'Approval (Softwarecatalogs)' .Yes
)
) > 0, DisplayMode.Edit, Disabled
)
Collect all Data when the PowerApp gets started:
Collect(col_Software,Softwarecatalogs)
PowerShell:
- PASoftwareOrder_CheckClient
This runbook will check if the Client exists in your Environment
[CMDletBinding()]
param
(
[String]
$DeviceName
)
$SiteCode = "xxx"
$ProviderMachineName = "xxx.contoso.com"
$initParams = @{}
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" @initParams
}
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
}
Set-Location "$($SiteCode):\" @initParams
$Device = Get-CMDevice -Name $DeviceName
$ResourceID = $Device.ResourceID
if ($ResourceID)
{
}
else
{
$ErrorActionPreference = "Stop"
Write-Error -Message "This Device $DeviceName does not Exist at your Environment"
}
2. PA_SoftwareOrder_CheckCollection
This runbook will check if the Client is already got an direct rule on the collection
[CMDletBinding()]
param
(
[String]
$DeviceName,
[String]
$CollectionID
)
$SiteCode = "xxx"
$ProviderMachineName = "xxx.contoso.com"
$initParams = @{}
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" @initParams
}
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
}
Set-Location "$($SiteCode):\" @initParams
$Device = Get-CMDevice -Name $DeviceName
$ResourceID = $Device.ResourceID
$DirectRules = Get-CMCollectionDirectMembershipRule -CollectionId $CollectionID
foreach ($item in $DirectRules)
{
if ($item.RuleName -eq $DeviceName)
{
$ErrorActionPreference = "Stop"
Write-Error -Message "The Device is already part of this Collection"
}
else
{
}
}
- PA_SoftwareOrder_Order
This Runbook will create an Direct Rule for the Device
[CMDletBinding()]
param
(
[String]
$DeviceName,
[String]
$CollectionID
)
$SiteCode = "xxx"
$ProviderMachineName = "xxx.contoso.com"
$initParams = @{}
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" @initParams
}
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
}
Set-Location "$($SiteCode):\" @initParams
$Device = Get-CMDevice -Name $DeviceName
$ResourceID = $Device.ResourceID
Add-CMDeviceCollectionDirectMembershipRule -CollectionId $CollectionID -ResourceId $ResourceID
If you got any questions left please feel free to contact me and if you need some help to implement a solution like this please contact your Customer Success Account Manager (CSAM) 🙂
Fabian Schlagenhaufer
CE
Disclaimer
The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.