<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>UK Platforms</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/ukplatforms/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/ukplatforms/feed.xml</link>
    <description>Latest updates from the Cloud &amp;amp; Infrastructure and Devices &amp;amp; Mobility Teams</description>
    <lastBuildDate>Thu, 14 Mar 2019 23:03:35 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>SCCM: COLLECTION DASHBOARD REPORT</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/sccm-collection-dashboard-report</link>
      <pubDate>Mon, 04 Mar 2019 19:30:28 GMT</pubDate>
      <dc:creator><![CDATA[Matt Balzan]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=3235</guid>
      <description><![CDATA[Hello! My name is Matt Balzan and I am a PFE with the Windows and Devices team, in the UK. I was...]]></description>
      <content:encoded><![CDATA[Hello! My name is Matt Balzan and I am a PFE with the Windows and Devices team, in the UK. I was recently at a customer where they asked me if I could analyse why their collections were taking so long to evaluate (at least 4 hours!).

After digging about in their database I found they had way too many collections with Incremental Updates or collections with hardly any members in them - this resulted in a massive performance hit on their database.

In total there were:
<ul>
 	<li>over 11000 collections</li>
 	<li>over 8000 with fewer than 5 members or less</li>
 	<li>some crazy sql queries taking over an hour long to run - not good! :(</li>
</ul>
As we know SCCM logs everything in logfiles and its own SQL database. Therefore all that was required was to create a dashboard to display the following information:
<ul>
 	<li>total collections in environment (CAS or Primary standalone)</li>
 	<li>collections with less than 5 members (this links to a sub-report)</li>
 	<li>all sites collections taking less than 10 seconds to evaluate</li>
 	<li>all sites collections taking between 10 &amp; 20 seconds to evaluate</li>
 	<li>all sites collections taking longer than 20 seconds to evaluate</li>
 	<li>highest collection modifier</li>
 	<li>weekly collection modifiers and their totals</li>
 	<li>collection refresh types count</li>
 	<li>WQL &amp; SQL query script used per collection and more...</li>
</ul>
And here it is. See content and use explained below.

&nbsp;

<strong>TOP &amp; MID SECTION</strong>

This report is split into 3 parts:

The TOP section displays the evaluation times which range from 0-10, 10-20 and 20 and above in seconds. The yellow and red charts are hyper-linked to the tables shown in the bottom section.

The MID section displays the Collection Refresh Types, Total Collections and Collections with less than 5 members, the Highest Collection Modifier plus some modifier details. The Collection Refresh Types is also hyper-linked to table in the BOTTOM section.

&nbsp;

<a href="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_MAIN_RD.jpg"><img width="1024" height="531" class="alignnone size-large wp-image-3255" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_MAIN_RD-1024x531.jpg" /></a>

&nbsp;

<strong>BOTTOM SECTION</strong>

Tables displayed:
<ul>
 	<li>COLLECTION EVALUATIONS &gt;20 SECS</li>
 	<li>COLLECTION EVALUATIONS 10-20 SECS</li>
 	<li>COLLECTION INCREMENTAL &amp; FULL SCHEDULED REFRESH TYPES</li>
</ul>
All tables include a legend with the following detail:

<em>S: Site Code | ID: Collection ID | CN: Collection Name | MC: Member Changes | M: Member Count | LMCT: Last Member Change Time | CS: Current Status | Q: Query | T: Time Evaluation (seconds)</em>

&nbsp;

<a href="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_COLL_20UP_RD.jpg"><img width="1024" height="708" class="alignnone size-large wp-image-3275" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_COLL_20UP_RD-1024x708.jpg" /></a>

<strong> <a href="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_COLL_10T20_RD.jpg"><img width="1024" height="732" class="alignnone size-large wp-image-3265" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_COLL_10T20_RD-1024x732.jpg" /></a><a href="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_COLL_10T20_RD.jpg"></a></strong>

The following table shows the legend with this detail:

<em>ID: Collection ID | CN: Collection Name | LCN: Limit To Collection Name | M: Member Count | RF: Refresh Type</em>

<a href="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_REFRESH_TYPES_RD.jpg"><img width="1024" height="237" class="alignnone size-large wp-image-3295" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_REFRESH_TYPES_RD-1024x237.jpg" /></a>

&nbsp;

<strong>SUB REPORT – COLLECTIONS LESS THAN 5 MEMBERS</strong>

This report will show you:
<ul>
 	<li>total of collections with 5 members or less</li>
 	<li>chart showing you all the collections with their member count total</li>
 	<li>table with a legend showing the following detail:</li>
</ul>
<em>ID: Collection ID | CN: Collection Name | M: Member Count | CT: Collection Type | CS: Current Status | LCN: Limit To Collection Name | RF: Refresh Type</em>

&nbsp;

<a href="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_RD.jpg"><img width="1024" height="427" class="alignnone size-large wp-image-3285" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_SubReport_RD-1024x427.jpg" /></a>

&nbsp;

<strong>HOW TO USE THESE REPORTS</strong>
<ol>
 	<li>Upload both reports in same root folder/path in your SSRS server.</li>
 	<li>Update your datasource for both rdl reports.</li>
 	<li>Ensure you have SELECT permissions for the tables mentioned below (or you or your DBA can use the sql script below).</li>
</ol>
&nbsp;
<p style="padding-left: 120px"><em>GRANT SELECT ON [Collection_Rules_SQL] TO [smsschm_users]</em></p>
<p style="padding-left: 120px"><em>GO</em></p>
<p style="padding-left: 120px"><em> </em></p>
<p style="padding-left: 120px"><em>GRANT SELECT ON [Collections_L] TO [smsschm_users]</em></p>
<p style="padding-left: 120px"><em>GO</em></p>
<p style="padding-left: 120px"><em> </em></p>
<p style="padding-left: 120px"><em>GRANT SELECT ON [Collections_G] TO [smsschm_users]</em></p>
<p style="padding-left: 120px"><em>GO</em></p>
&nbsp;

<strong>Download the reports from here:</strong> [<a href="https://msdnshared.blob.core.windows.net/media/2019/03/Reports2.zip">Collection_Dashboard_Reports</a>]

&nbsp;

<em><strong>TIPS</strong></em>
<ol>
 	<li>Using the Report Builder, you can set the report to auto refresh by applying the setting in seconds:</li>
</ol>
&nbsp;

<img width="447" height="485" class="size-full wp-image-3245 aligncenter" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/03/SCCM_COLL_DASH_AutoRefresh.jpg" />

&nbsp;
<ol start="2">
 	<li>Also, check above if your Language is set accordingly to ensure the correct date/time format! Since I am in the UK, mine is set to en-GB</li>
</ol>
&nbsp;

Now you can start troubleshooting those problematic collections, begin tackling the ones that are taking too long to evaluate and investigate why the SQL/WQL queries are taking too long to return the desired data.

Hope this helps with your SCCM Collection housekeeping! In the next blog I will show you how to fix the issues with your collections and the best practices to keep your environment in good shape.

😊

++Special kudos to Ross Moore &amp; <span>Doug Varner for testing this in their environments!</span>

<strong>DISCLAIMER</strong>
The sample files are not supported under any Microsoft standard support program or service. The sample files 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 files and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the files 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.

<strong>Matt Balzan | Premier Field Engineer | SCCM, Application Virtualisation</strong>

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>INTUNE: REPORT ALL DEVICES THAT ARE NON-COMPLIANT BECAUSE THEY ARE INACTIVE</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/intune-report-all-devices-that-are-non-compliant-because-they-are-inactive</link>
      <pubDate>Sat, 23 Feb 2019 16:42:44 GMT</pubDate>
      <dc:creator><![CDATA[AnilAbraham]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=3125</guid>
      <description><![CDATA[It’s me Anil Abraham again with a quick blog. These days my job involves walking into customers and...]]></description>
      <content:encoded><![CDATA[<p style="text-align: left"><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">It’s me Anil Abraham again with a quick blog. These days my job involves walking into customers and talking about modern device management and how to shift from the traditional management methods to the new ways. Obviously, Intune plays a big part in modern device management.</span></span></p>
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">More often these days, I get asked the question of how about reporting and the flexibility we had with SCCM in terms of capability that came with SSRS that let us create custom reports.</span></span>

<b><u><span style="margin: 0px;padding: 0cm;border: 1pt windowtext;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Customer Query:-</span></span></u></b><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">The other day one of the customers asked me a question, how to report all devices in Intune that are reported as non-compliant because they have not reported back to Intune in the last 30 days. 30 days because in Intune that is the default setting for a device to be marked non - compliant if it hasn't checked in.</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Microsoft Graph is your answer. For those that are new to Microsoft Graph start @ https://developer.microsoft.com/en-us/graph/</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">The data that Intune exposes, be it inventory data, policy or apps, they are all exposed via Microsoft Graph.</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">For people familiar with Graph and Intune, the query that gives the result is</span></span>
<blockquote>
<p style="text-align: left"><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">https://graph.microsoft.com/v1.0/deviceManagement/
deviceCompliancePolicySettingStateSummaries/
DefaultDeviceCompliancePolicy.RequireRemainContact/
deviceComplianceSettingStates?$filter=state eq 'nonCompliant'</span></span></p>
</blockquote>
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">For people who are new to this...</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Sign into Graph Explorer (<a href="https://developer.microsoft.com/graph/graph-explorer/">https://developer.microsoft.com/graph/graph-explorer/</a>) using an account that has delegated permissions to read the properties and classes that you are after. More details @ </span></span><a href="https://docs.microsoft.com/en-us/graph/permissions-reference"><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt">https://docs.microsoft.com/en-us/graph/permissions-reference</span></a><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">For a quick view of the data that is held within Intune and exposed via Graph, an example of all the enrolled devices is as below.</span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorer_Devices.jpg"><img width="2513" height="987" class="alignleft size-full wp-image-3135" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorer_Devices.jpg" /></a><a href="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorer_Devices.jpg"></a>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000"><strong><u>Getting there...</u></strong>
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">
I started looking at the API for Intune @ <a href="https://docs.microsoft.com/en-us/graph/api/resources/intune-graph-overview?view=graph-rest-1.0">https://docs.microsoft.com/en-us/graph/api/resources/intune-graph-overview?view=graph-rest-1.0</a></span></span>
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">
From the request, I know, I am after a specific device compliance setting, so first step is to find all the device compliance policy settings, which I found was listed @ https://docs.microsoft.com/en-us/graph/api/intune-deviceconfig-devicecompliancepolicysettingstatesummary-get?view=graph-rest-1.0
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">
From the above I could identify the query to be run in Graph Explorer as https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicySettingStateSummaries
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">
This gives me a list of settings that are part of compliance policies as below in the graph explorer.</span></span></span></span></span></span></span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/CompliancePolicySettings.jpg"><img width="2511" height="1250" class="alignleft size-full wp-image-3145" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/CompliancePolicySettings.jpg" /></a>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Going through the list I could identify the value that I was after as below.</span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/SettingJSON.jpg"><img width="956" height="396" class="alignleft size-full wp-image-3155" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/SettingJSON.jpg" /></a>

&nbsp;

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">I hear you ask, what is so much about it as there is this list you could Export from the Intune Web console. A sample from test tenant is below. The intention of this blog is to give an overview of the Graph and access to data within.</span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/IntuneReporting.jpg"><img width="2508" height="1182" class="alignleft size-full wp-image-3165" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/IntuneReporting.jpg" /></a>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">As you can see the numbers from the graph explorer matches what you see for the "Is Active" setting. You also have the option to export the report at this level. You could drill down further in the Intune Report as below.</span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/IntuneReporting1.jpg"><img width="2487" height="1267" class="alignleft size-full wp-image-3175" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/IntuneReporting1.jpg" /></a>

&nbsp;

<span style="font-family: 'Times New Roman',serif"><span style="font-size: x-large"><span style="color: #000000"><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span>There is an export option here too and you could get the data into Excel but it gives you all the devices, both compliant and non-compliant and then you should filter in Excel to get the data that you are after.</span></span></span></span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Let’s look at how to do all this in Graph Explorer. From the previous exercise in Graph Explorer we know the ID of the setting that we are after is DefaultDeviceCompliancePolicy.RequireRemainContact.</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Further constructing the query to report only on the above said setting and devices to show only that compliance setting value, I built the query as below</span></span>
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">
https://graph.microsoft.com/v1.0/deviceManagement/
deviceCompliancePolicySettingStateSummaries/
DefaultDeviceCompliancePolicy.RequireRemainContact/deviceComplianceSettingStates
</span></span>
<span style="margin: 0px;line-height: 107%;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Result is as below</span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorer_IsActive.jpg"><img width="2506" height="1290" class="alignleft size-full wp-image-3185" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorer_IsActive.jpg" /></a>

&nbsp;

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">The result shows all the 12 devices in my test tenant and the compliance state for each of those devices against the DefaultDeviceCompliancePolicy.RequireRemainContact setting.</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Sample data for a device that is non-compliant is as below. </span></span><a href="https://msdnshared.blob.core.windows.net/media/2019/02/nonCompliant30dayJSON.jpg"><img width="1012" height="514" class="alignleft size-full wp-image-3195" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/nonCompliant30dayJSON.jpg" /></a>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">As you can see the value that we are after is the value of the property "state". So, with that knowledge building the query to show only the devices that are non-compliant for that setting would be </span></span>
<blockquote><span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicySettingStateSummaries/DefaultDeviceCompliancePolicy.RequireRemainContact/deviceComplianceSettingStates?$filter=state eq 'nonCompliant'</span></span></blockquote>
<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">In action the result would look like below.</span></span>

<a href="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorerFilternon-Compliant.jpg"><img width="2511" height="1294" class="alignleft size-full wp-image-3205" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/02/GraphExplorerFilternon-Compliant.jpg" /></a>

&nbsp;

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">
The result is the 9 devices that are non-compliant because they have not contacted Intune for the last 30 days.</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">But, I can hear you say, Anil I want to report all this programmatically and reporting through Graph Explorer is not an option in an enterprise. If there is enough interest and I can find some time I will put a Part 2 to show how we could automate all this.</span></span>

<span style="margin: 0px;font-family: 'Times New Roman',serif;font-size: 15pt"><span style="color: #000000">Hope you find this helpful....</span></span>]]></content:encoded>
    </item>
    <item>
      <title>Office 365 ProPlus - How To: Add previously excluded components</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/office-365-proplus-how-to-add-previously-excluded-components</link>
      <pubDate>Fri, 08 Feb 2019 19:40:03 GMT</pubDate>
      <dc:creator><![CDATA[NandanSheth]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=3045</guid>
      <description><![CDATA[If you have installed a Office 365 ProPlus with a custom XML file with excluded components and then...]]></description>
      <content:encoded><![CDATA[<h4><strong><span style="text-decoration: underline">If you have installed a Office 365 ProPlus with a custom XML file with excluded components and then struggled to add components that users needed, this post should help you with the process of pushing additional components to managed devices.</span></strong></h4>
<span>Hello, Nandan here again. When I last wrote a blog post I was based in Reading, UK and have since relocated to Dublin. It’s been a busy but interesting last few months with the transition. </span>

<span>Back working with customers, helping them with their move to a modern desktop, I ran into an interesting query last week. One of my customers is in the process of deploying Windows 10 with Office 365 ProPlus. As part of the operating system deployment, they are deploying a generalized Office configuration. The XML file that is being used to configure the Office client excludes a specific set of components and the setup process works exactly as it is expected to. The components that are excluded are not available on the system. Here is a snippet of the XML file to show the components that are not being deployed.</span>

<img src="https://msdnshared.blob.core.windows.net/media/2019/02/Image1_OriginalExclusions.png" alt="" width="955" height="154" class="size-full wp-image-3055 aligncenter" />

<span>As you can see, we are excluding Access, Skype for Business and both OneDrive components - OneDrive Desktop and OneDrive Groove. </span>

<span>Now, certain users within the organization need to work with Access databases. So obviously, they will need Access on their systems. The business wants to make Access available to users on demand when they need it and not have it installed as a standard component of Office. This is understandable and a common practice. To perform this installation, we have created a second XML file which does not have the exclusion for Access.</span>

&nbsp;

<img src="https://msdnshared.blob.core.windows.net/media/2019/02/Image2_RevisedExclusions.png" alt="" width="956" height="104" class="aligncenter size-full wp-image-3065" />

<span>In the first instance, Office is installed successfully without Access. In the second instance, with the modified XML file, setup runs and when the over-the-top install completes Access is still missing. </span>

<span>So, how do we resolve this problem to ensure Access is deployed for users that have a need for this component? </span>

<span>As it turns out, there are two ways to do this.</span>

<strong><span>Simple and Dirty: Install Access as an Independent Application </span></strong>

<span>Create a new XML:</span>

<img src="https://msdnshared.blob.core.windows.net/media/2019/02/Image3_AccessRetail.png" alt="" width="944" height="69" class="aligncenter size-full wp-image-3075" />

<span>We use <strong>AccessRetail</strong> as the Product ID. 16.0.9126.2295 - the Version element -  is an Office 365 ProPlus Semi Annual Channel build from the 1803 release of Office 365 ProPlus and when Access is installed, it is the right branch and version as needed.</span>

<span>This is the dirty approach because now, Office 365 ProPlus and Access 2016 show up as two separate applications in the Add Remove Programs dialog box. Access might need separate patch management and also, I am not 100% sure if Access will update in line with how other O365 ProPlus components would. I haven't used the Office 365 ProPlus components with this configuration and this will need a considerable amount of testing to ensure things work as expected.  Finally, if you are using multiple language packs, there will be one entry for each language pack. In our case, we had 18 entries in Add Remove Programs because we were using 9 language packs (this will be cleaned up in a separate exercise). Cosmetically, this is a nightmare.</span>

<img src="https://msdnshared.blob.core.windows.net/media/2019/02/Image4_AppwizCpl.png" alt="" width="866" height="354" class="aligncenter size-full wp-image-3085" />

&nbsp;

<strong><span>The Cleaner Way </span></strong>

<span>To begin with, I modified my second XML file to add logging for the Office 365 ProPlus installation.</span>

<img src="https://msdnshared.blob.core.windows.net/media/2019/02/Image5_LoggingElement1.png" alt="" width="960" height="261" class="aligncenter size-full wp-image-3105" />

&nbsp;

<span>I also used SysInternal’s Process Monitor to track any file and registry activity that was happening on the system while an over-the-top-install was running to install the missing Access components. </span>

<span>The logs revealed some interesting information. About three seconds in after calling setup.exe, we see the following information in the log file: </span>
<blockquote>
<pre>01/21/2019 11:46:06.522      SETUP (0x28ec) 0x1b0c        Click-To-Run General Telemetry      aoh9t       Medium TryLaunchClient::HandleStateAction {"MachineId": "71f7769c1fca0c488833235052d1f416", "SessionID": "1eb0e35a-e57c-4051-b6a2-497355a89d17", "GeoID": 242, "Ver": "16.0.9126.2295", "C2RClientVer": "16.0.9126.2295", "ContextData": "{\"message\":\"Launching executable\",\"ClientExeName\":\"OfficeClickToRun.exe\",\"Parameters\":\"culture=en-us defaultplatform=False allowcdnfallback=False deliverymechanism=7ffbc6bf-bc32-4f92-8982-f9dd17fd3114 platform=x86 productreleaseid=none forcecentcheck= lcid=1033 b= storeid= piniconstotaskbar=FALSE autoactivate=1 productstoadd=O365ProPlusRetail.16_en-us_zh-cn_cs-cz_hu-hu_ms-my_pl-pl_ko-kr_sk-sk_th-th_x-none <strong>scenario=CLIENTUPDATE</strong> updatesenabled=True acceptalleulas=True cdnbaseurl=http://officecdn.microsoft.com/pr/7ffbc6bf-bc32-4f92-8982-f9dd17fd3114 version=16.0.9126.2295 sclcacheoverride=0 mediatype=Local baseurl=\\\"C:\\\\Users\\\\username\\\\file_location\\\\OfficeC2R\\\" <strong>o365proplusretail.excludedapps=groove,lync,onedrive </strong>sharedcomputerlicensing=0 flt.useexptransportinplacepl=unknown flt.useoutlookshareaddon=unknown flt.useofficehelperaddon=unknown\"}"}</pre>
</blockquote>
<span>We can see that setup is correctly identifying that Access should be installed. However, the end result is not aligned with our requirements or expectations. </span>

<span>A process monitor trace gives us some more information.</span>
<blockquote>
<pre>11:46:06.7572701      OfficeClickToRun.exe  684       10964  RegSetValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\CLIENTUPDATE\ExcludedApps       SUCCESS Type: REG_SZ, Length: 78, Data: o365proplusretail_groove,lync,onedrive
11:46:12.0268475      OfficeClickToRun.exe  10468       1264   RegSetValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL\ExcludedApps       SUCCESS Type: REG_SZ, Length: 78, Data: o365proplusretail_groove,lync,onedrive
11:46:12.1683621      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL\ExcludedApps       SUCCESS Type: REG_SZ, Length: 78, Data: o365proplusretail_groove,lync,onedrive
11:46:12.1683934      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL\ExcludedApps       SUCCESS Type: REG_SZ, Length: 78, Data: o365proplusretail_groove,lync,onedrive
<strong>11:46:12.1685581      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\O365ProPlusRetail.ExcludedApps       SUCCESS Type: REG_SZ, Length: 56, Data: access,groove,lync,onedrive</strong>
<strong>11:46:12.1686073      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\O365ProPlusRetail.ExcludedApps       SUCCESS Type: REG_SZ, Length: 56, Data: access,groove,lync,onedrive</strong>
<strong>11:46:12.1688617      OfficeClickToRun.exe  10388       10132  RegSetValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL\ExcludedApps       SUCCESS Type: REG_SZ, Length: 92, Data: O365ProPlusRetail_access,groove,lync,onedrive</strong>
<strong>11:46:12.1902420      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL\ExcludedApps       SUCCESS Type: REG_SZ, Length: 92, Data: O365ProPlusRetail_access,groove,lync,onedrive</strong>
<strong>11:46:12.1902609      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL\ExcludedApps       SUCCESS Type: REG_SZ, Length: 92, Data: O365ProPlusRetail_access,groove,lync,onedrive</strong>
<strong>11:46:12.1903673      OfficeClickToRun.exe  10388       10132  RegSetValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\O365ProPlusRetail.ExcludedApps       SUCCESS Type: REG_SZ, Length: 56, Data: access,groove,lync,onedrive</strong>
<strong>11:46:12.1913449      OfficeClickToRun.exe  10388       10132  RegQueryValue HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\O365ProPlusRetail.ExcludedApps       SUCCESS Type: REG_SZ, Length: 56, Data: access,groove,lync,onedrive</strong></pre>
</blockquote>
<span>Setup.exe calls into OfficeClickToRun.exe which is querying the ExcludedApps value from the initial install. It sees that Access was excluded in the initial deployment, and then reconfigures the Configuration\O365ProPlusRetail.ExcludedApps to reflect the original configuration that excluded Access. The setup process then completes without enabling the components for Access and it does not show up as installed. </span>

<span>To resolve the issue, we ended up using a simple batch file that first modified the registry before invoking setup.exe. As you can see below, this really as simple as simple can get. </span>
<blockquote>
<pre><span>===== START BATCH FILE ===== </span>

reg.exe add HKLM\SOFTWARE\Microsoft\Office\ClickToRun\Configuration /v O365ProPlusRetail.ExcludedApps /d REG_SZ /v groove,lync,onedrive /f
setup.exe /configure .\OfficeC2R_1803_Access_Config.xml

<span>===== END BATCH FILE =====</span></pre>
</blockquote>
<span>This works great and when setup.exe completes an over-the-top install, Access is included on the Office deployment and works just as expected.</span>

<span>On my side, I am trying to see if I can reproduce this problem on a test environment and then flag this with the product group for some more investigation as I have seen threads on various forums where people have experienced similar problems. Once I have an update, I will refresh the content of this post with relevant information.</span>

<span>In the meantime… </span>
<ol>
 	<li><span>If you experience any inconsistencies with Office 365 ProPlus installations, irrespective of which build you are installing, see if you are using the latest version of the Office Deployment Tools. The tooling is update frequently, and can be downloaded from <a href="https://www.microsoft.com/en-us/download/details.aspx?id=49117">HERE</a>. The latest version is 16.0.11306.33602, dated 14<sup>th</sup> January 2019 (at the time this post was written) and which is what we used while trying to crack this problem. </span></li>
 	<li><span>If you need to deploy Office 365 ProPlus and need to author your configuration.xml files, try using <a href="https://config.office.com">https://config.office.com</a>. This is a great online tool that lets you create your configuration files without having to remember the tags and structure of the XML file. </span></li>
 	<li><span>For more details on Office Customization, refer to <a href="https://docs.microsoft.com/en-us/DeployOffice/overview-of-the-office-customization-tool-for-click-to-run">THIS</a>. </span></li>
 	<li>And finally, refer to this link for the <a href="https://docs.microsoft.com/en-us/DeployOffice/configuration-options-for-the-office-2016-deployment-tool">configuration options for the office deployment tool</a>.</li>
</ol>]]></content:encoded>
    </item>
    <item>
      <title>5 Second Delays Add Up – The Case of the 4 Hour + In-Place Upgrade</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/5-second-delays-add-up-the-case-of-the-4-hour-in-place-upgrade</link>
      <pubDate>Mon, 18 Jun 2018 14:39:44 GMT</pubDate>
      <dc:creator><![CDATA[NandanSheth]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2895</guid>
      <description><![CDATA[[How does simple configuration oversight affect in-place upgrades for Windows 10? Read on to find...]]></description>
      <content:encoded><![CDATA[<strong>[How does simple configuration oversight affect in-place upgrades for Windows 10? Read on to find out.]</strong>

Hello, Nandan here again!

Over the last couple of years, as businesses have started adopting <span><a href="https://docs.microsoft.com/en-us/windows/deployment/update/waas-overview">Windows as a Service</a></span>, one of the big changes in managing the Windows life-cycle is the in-place upgrade. Clearly, with the shorter release cadence, it isn’t possible to “rebuild” a user’s device every time a new build of Windows 10 is approved for deployment within the organization. So, we upgrade in-place from 1703 to 1709 to 1803 and so on.

One of the most common problems that I have seen with this approach is the amount of time it takes for an in-place upgrade to complete. In an ideal scenario, this process should take between 45 minutes to an hour. This is the time I have seen on my personal desktop at home, which is a stand-alone, unmanaged device. This is also the unofficial guide time for the in-place upgrade. I say unofficial, because it’s very difficult to put a finger on exactly how long the in-place upgrade should take. There are various factors involved. How many users are using the machine, and how many profiles do we have on the system being upgraded? How large are these user profiles? Where is the user data stored? Is the AV configured for real-time scanning for write operations (because a lot of data is being written to the disk during the in-place upgrade, real-time scanning can directly influence the time this process takes)?

At a very high level, the following steps are involved in the upgrade process:
<ul>
 	<li>The Windows 10 OS image is downloaded</li>
 	<li>The Windows setup engine runs through various compatibility checks as a way to ensure there are no known issues with applications, drivers, BIOS, etc</li>
 	<li>User data is inventoried; this is needed so that it can be restored at the end of the upgrade process</li>
 	<li>Setup then completes the actual upgrade of the operating system – this process involves laying out the new operating system on to the disk and moving the old operating system out of the way</li>
</ul>
I will be following up with a separate post that dissects the in-place upgrade in a few weeks.

A few months ago, one of the customers I was working with sent me an email. They were transitioning from 1607 to 1709 and had observed that in-place upgrades were taking over 4 hours plus at the longest, with an average time somewhere between 2.5 to 3 hours. In either case, compared to the normally expected upgrade times, this was way off the mark. To get to the bottom of this, we donned our troubleshooting hats and began picking through the logs to see where the delays were and how we could bring the upgrade times to an acceptable value.

The test machines we were using were shared systems with multiple user profiles. User files and documents were redirected to a NAS device and a few of them had their Documents folder as large as 25 GB.

We started with the most important log that is used for issues in the Windows setup process – the setupact.log file. This file is found under the <strong>C:\$Windows.~BT\Sources\Panther</strong> folder. The folder is normally hidden, so you might need to toggle the settings for hidden files and folders from Explorer options if you don’t see it.

The customer was using an in-place upgrade task sequence in SCCM to perform the upgrade. The delays we see below were observed after the operating system image download had completed and setup.exe had been invoked.

The big delays were happening after the OS finished downloading the image and the first reboot, and then post the first reboot.
<ul>
 	<li>Time taken after the OS image was downloaded to first reboot: 150 minutes</li>
 	<li>Time taken for the upgrade to complete after the first reboot: 100 minutes</li>
</ul>
The log started showing some very clear patterns. Below is a snippet showing 5 second delays:

<img src="https://msdnshared.blob.core.windows.net/media/2018/06/IPU_Delays_Logs_1.jpg" alt="" width="692" height="262" class="aligncenter wp-image-2915" />

The next snippet shows similar delays, but this time the delays were up to 10 seconds.

<img src="https://msdnshared.blob.core.windows.net/media/2018/06/IPU_Delays_Logs_2.jpg" alt="" width="669" height="224" class="aligncenter wp-image-2925" />

There are a couple of things to note here:
<ol>
 	<li>What are those strange characters that we see in the file names? This is out of scope for the current discussion, and I will revisit this when I work on the dissection of the in-place upgrade.</li>
 	<li>And probably, the bigger question - what is happening during these 5 or 10 second delays?</li>
</ol>
Considering we are accessing a network share, my first thought was this could be a networking issue. Two tools popped out from the arsenal – Procmon and Network Monitor. I decided to start with Procmon.

We started running setup for the upgrade, manually this time, because we knew that the actual download of the image was not causing any delays. By running setup from a USB stick, we were able to speed up the troubleshooting process. After starting setup, we monitored the setupact.log file for the phase where user data is processed, which is where we see the delays above and then started a Procmon trace. For the delays, the trace showed…

<img src="https://msdnshared.blob.core.windows.net/media/2018/06/IPU_Delays_Logs_3.jpg" alt="" width="685" height="284" class="aligncenter wp-image-2965" />

The status code for these operations is 0xC0000199. This translates to:

<img src="https://msdnshared.blob.core.windows.net/media/2018/06/Error-Code-Translation.jpg" alt="" width="488" height="108" class="aligncenter wp-image-2975" />

And, these operations were performed by setuphost.exe, which was running under the context of the System account.

<img src="https://msdnshared.blob.core.windows.net/media/2018/06/IPU_Delays_Procmon.jpg" alt="" width="480" height="542" class="aligncenter wp-image-2905 " />

On digging around further, I found that the path to the network share was using the CNAME record for the NAS device hosting the file shares. In the paths we see in the snippets above, SERVER is an alias, not the actual hostname. So, we started testing this further. On a test system, we opened a command window running under the context of the system account (PSExec to the rescue).
<ul>
 	<li>Try to map a drive using the A record for the NAS device – Successful</li>
 	<li>Try to map a drive using the CNAME record for the NAS device – 0xC0000199 after 5 seconds</li>
</ul>
That second result is the same symptom we were seeing during the In-Place Upgrade.

This got us looking at authentication options for the NAS and how the appliance was generally configured. The customer was using Kerberos authentication for the NAS device, and the problem seems to be related to issues with how authentication options and SPNs are registered for the records that point back to the NAS. We found an interesting article on the NetApp website (the customer was also using a NetApp filer for their storage requirements).

<span><a href="https://kb.netapp.com/app/answers/answer_view/a_id/1030858">https://kb.netapp.com/app/answers/answer_view/a_id/1030858</a></span>

The final resolution was missing SPNs. For the name in the A record there were SPNs registered. For the name in the CNAME record, there were no SPNs registered. Registering SPNs for the CNAME record got the connections to the shares working, using the alias. Once this was done, upgrades started completing in a 45 to 60 minute range – which is what the normally expected times for an in-place upgrade are and what the customer was also expecting.

For those that don’t normally work on DNS and networking – an easy way to understand the CNAME record is a nickname or an alias.
<ul>
 	<li>Name to IP Address mapping – A Record</li>
 	<li>Alias to Name mapping – CNAME Record</li>
</ul>
For example, your name is Jonathan and your phone number is 123456. Friends and family know you as John. In DNS terms, this would be stored as:
<ul>
 	<li>Jonathan (A Record) 123456</li>
 	<li>John (CNAME Record) Jonathan</li>
</ul>
Anyone looking for the phone number can look for John or Jonathan, and they will receive the same number.

In the same way, when you create a DNS name record for your NAS filer, it might map the IP address to a complex name like filer-xxxxxx-<em>serial_number</em>.domain.com. However, you might want to make it easier to access the NAS device and just call it fileserver.domain.com. In this case, you end up with:
<ul>
 	<li>filer-xxxxxx-<em>serial_number</em>.domain.com  (A Record) 192.168.100.120</li>
 	<li>filerserver.domain.com (CNAME Record) filer-xxxxxx-<em>serial_number</em>.domain.com</li>
</ul>
Which means…

<strong>fileserver.domain.com</strong> maps to <strong>filer-xxxxxx-serial_number.domain.com</strong> maps to <strong>IP address</strong>

Ultimately, the CNAME or the A record map to the same underlying IP address.

Here’s a good article describing these records - <span><a href="https://support.dnsimple.com/articles/differences-between-a-cname-alias-url/">https://support.dnsimple.com/articles/differences-between-a-cname-alias-url/</a> </span>

For information on Service Principal Names, review - <span><a href="https://msdn.microsoft.com/en-us/library/ms677949(v=vs.85).aspx">https://msdn.microsoft.com/en-us/library/ms677949(v=vs.85).aspx</a></span>]]></content:encoded>
    </item>
    <item>
      <title>Say No to Long Term Servicing Channel (LTSC)</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/say-no-to-long-term-servicing-channel-ltsc</link>
      <pubDate>Mon, 11 Jun 2018 12:54:47 GMT</pubDate>
      <dc:creator><![CDATA[Greg Nottage]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2885</guid>
      <description><![CDATA[Hello, my name is Greg Nottage and I am a Consultant with the Windows and Devices team in the...]]></description>
      <content:encoded><![CDATA[Hello, my name is Greg Nottage and I am a Consultant with the Windows and Devices team in the UK.

I often work with customers who are in the process of transitioning to Windows 10 but are considering the use of Windows 10 Long Term Servicing Channel (often shortened to LTSC and also previously known as Long Term Servicing branch which was shortened to LTSB – thanks Marketing team :-).

When we look at managing Windows 10 through the same lens as we’ve all done for the past 20 years or so (i.e. a new operating system release cycle of approximately one every 3 to 5 years), it's easy to see why customers would think LTSC is the right solution for them,.  However, with the launch of Windows 10, Microsoft introduced a new concept for release management – Windows as a Service (or WaaS for short).  I won’t dwell on the details of the concept, as it’s been <u><a href="https://docs.microsoft.com/en-us/windows/deployment/update/waas-quick-start">well documented</a></u> before – but I will say that, in a nutshell, WaaS represents an increased frequency of change (given the bi-annual release cycle that is the new normal).  Organisations do need to prepare for WaaS appropriately to ensure the best success - but once correctly implemented, the mid- to long-term results will reduce IT overhead as the changes being introduced are incremental in nature.

LTSC has its place – specifically it is meant for devices that have single (often mission-critical) use-cases such as:
<ul>
 	<li>Systems that power manufacturing or medical equipment</li>
 	<li>Embedded systems in kiosks, such as ATMs or airport ticketing systems</li>
</ul>
You might notice that the above examples are typical of ‘embedded style’ devices – and you would be right.  LTSC 2016 shares the same codebase as Windows 10 IoT, which is the evolution of Windows Embedded OS – and you wouldn’t have considered widely deploying Embedded OS everywhere.  The point of this article is to help to dispel any myths that may exist and to help you understand why wide adoption of LTSC is a bad idea.
<h3>Why not LTSC?</h3>
It may be tempting for organisations to look at LTSC and determine that this more closely matches the historic Windows release cycle, however the recommended Windows 10 version that should be widely deployed is the Semi-Annual Channel (SAC, formerly known as Current Branch for Business or CBB).

I’ve spoken to customers who held a misconception that the SAC version of Windows 10 prevented the permanent removal of certain standard applications that are pre-installed with Windows 10 (like the Xbox app etc.).  This is not correct and should not be a reason for adopting LTSC. Pre-installed applications can be removed and consumer features can be disabled. These activities have been covered by Michael Niehaus in his previous posts and I recommend customers bookmark these articles:
<ul>
 	<li><a href="https://blogs.technet.microsoft.com/mniehaus/2015/11/11/removing-windows-10-in-box-apps-during-a-task-sequence/">Removing Windows 10 In-Box Applications During A Task Sequence</a></li>
 	<li><a href="https://blogs.technet.microsoft.com/mniehaus/2015/11/23/seeing-extra-apps-turn-them-off/">Seeing Extra Apps? Turn Them Off</a></li>
 	<li><a href="https://blogs.technet.microsoft.com/mniehaus/2018/03/13/more-on-included-windows-10-apps/">More On Included Windows 10 Apps</a></li>
</ul>
As a general guideline, devices that fulfill the following criteria are considered general-purpose devices and should be paired with Windows 10 using the Semi-Annual Channel servicing option:
<ol>
 	<li>Devices that run productivity software such as Microsoft Office</li>
 	<li>Devices that use Windows Store applications</li>
 	<li>Devices that are used for general Internet browsing (for example, research or access to social media)</li>
</ol>
Note: Gartner agrees with this approach (see <u><a href="https://www.gartner.com/doc/reprints?id=1-3V8VD56&amp;ct=170309&amp;st=sb">Rethink Windows 10 LTSB Deployment Based on Microsoft's Updated Guidance</a>)</u>

It is also worth mentioning that if you have deployed Windows 10 LTSC, all is not lost.  It is possible to perform an in-place upgrade to the SAC version of Windows 10, which is the recommended solution to align with Microsoft guidance on the broad use of SAC instead of LTSC.

To further explain the reasoning behind the use of SAC over LTSC, please read the following key points:
<ol>
 	<li>Using LTSC means missing out on new OS enhancements that are included in SAC releases – particularly new security features</li>
 	<li>LTSC does not keep pace with new silicon releases in the same way SAC does – so LTSC 2016 does not support Intel chips beyond the ‘Kabylake’ generation</li>
 	<li>Windows Analytics Upgrade Readiness does not support LTSC</li>
 	<li>No support for the modern Edge browser</li>
 	<li>No support for Cortana</li>
 	<li>No support for Windows Store</li>
 	<li>No support for Surface hardware</li>
 	<li>LTSC does not support ConfigMgr Express Updates</li>
 	<li>In-Place Upgrade from Windows 7 to Windows 10 is not supported for LTSC</li>
 	<li>From January 2020, Microsoft Office 365 will not be supported on LTSC</li>
 	<li>LTSC does not keep pace with feature enhancements to Windows Defender ATP</li>
 	<li>Potential Independent Hardware and Software Vendor support and limitations on LTSC</li>
 	<li>Non-security operating system fixes and enhancements may not get back-ported to LTSC</li>
 	<li>Loosely defined LTSC release cycles make planning ahead more difficult</li>
</ol>
I hope this clarifies your understanding around the use Windows 10  LTSC and helps to explain the reasoning behind why you should deploy SAC, and not LTSC. If you have any questions or comments about this post, please leave a comment below.

EDIT (18-06-2018): A colleague has recommended I link to Todd Furst's excellent session from Ignite that also helps explain more on this subject - <a href="https://channel9.msdn.com/Events/Ignite/Microsoft-Ignite-Orlando-2017/THR2010R">https://channel9.msdn.com/Events/Ignite/Microsoft-Ignite-Orlando-2017/THR2010R</a>

EDIT(22-08-2018): A couple of colleagues noticed that the above Ignite link was no longer working, so I've amended it to one that is.  Many thanks to those who pointed this out.]]></content:encoded>
    </item>
    <item>
      <title>Deploy Custom GPO via Microsoft Intune</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/google-chrome-gpo-via-intune</link>
      <pubDate>Wed, 30 May 2018 09:56:54 GMT</pubDate>
      <dc:creator><![CDATA[AnilAbraham]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2705</guid>
      <description><![CDATA[Hello! My name is Anil Abraham, and I am a Senior PFE with the Windows and Devices team, in the...]]></description>
      <content:encoded><![CDATA[Hello! My name is Anil Abraham, and I am a Senior PFE with the Windows and Devices team, in the UK.

Modern management for Windows 10 is a hot topic and with Autopilot, Azure AD Join and management using Intune, a question that customers keep asking me is, "Would MDM ever replace Group Policy?" With a smile, I say, "I don't know." The next question is, "what if I have custom ADMX files, like those for Google Chrome!". The answer is, you should be using Microsoft Edge :)

But hey, you know we are Microsoft, we have great products and our engineers provide great solutions to help you. So, today, I want to illustrate how you can manage settings for third party applications with custom ADMX templates using Microsoft Intune. The application in focus for this post is Google Chrome.

Group policy templates for Google Chrome can be downloaded from <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://dl.google.com/dl/edgedl/chrome/policy/policy_templates.zip">here</a></span>.

Note: This is an external link and is subject to change.

The procedure below will use the concepts of ADMX backed MDM policies, the details of which are available <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://docs.microsoft.com/en-us/windows/client-management/mdm/understanding-admx-backed-policies">here</a></span>.

I understand how the ADMX policies are interpreted, especially the ones shipped with Windows. But what was of interest to me was the following statement:
“An ADMX file can either be shipped with Windows (located at %SystemRoot%\policydefinitions) or it can be ingested to a device through the Policy CSP URI (./Vendor/MSFT/Policy/ConfigOperations/ADMXInstall).”
<h2><strong>Ingesting an ADMX</strong></h2>
So essentially, our first step is to ingest the custom ADMX for Google Chrome through the Policy CSP.
This <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://docs.microsoft.com/en-us/windows/client-management/mdm/win32-and-centennial-app-policy-configuration">link</a></span> further explains how to ingest the ADMX template into the policy CSP and then subsequently apply the settings we want – in this example, the "Show Home Button" setting.
With all that knowledge let’s look at how this translates to an MDM policy in Intune.
The first step is to create Custom Windows 10 policy to ingest the ADMX as shown below. I am assuming you know how to do this. If you are new to this, refer to the <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://docs.microsoft.com/en-us/intune/custom-settings-windows-10">link</a></span> .

<a href="https://msdnshared.blob.core.windows.net/media/2018/05/Pic-1.png"><img width="2558" height="621" class="alignnone wp-image-2715 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/05/Pic-1.png" /></a>

Key points to note are as below:
<pre>OMA-URI :
 ./Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/GoogleChrome/Policy/AppAdmxFile01

Data Type:
 String

Value:
 The value for the string will be the contents of the policy set available in the Chrome ADMX template. Download the txt file from <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://gallery.technet.microsoft.com/OMA-URI-String-Value-for-ab37207c">here</a></span> and copy the contents to the String Value field.</pre>
Note: The OMA-URI has two attributes that are user defined. They are the application name (GoogleChrome in this case) and the ADMX file name (AppAdmxFile01 in this case). The ADMX file name will change based on the file you are using. As long as it is unique on the device, it does not matter what you have called the last part.
The URI determines whether the policy is a user or a device policy. This <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://docs.microsoft.com/en-us/windows/client-management/mdm/win32-and-centennial-app-policy-configuration">link</a></span> explains how you construct them for device or user. Once you have saved the policy assign it to group of your choice.
In my case I am assigning a machine level policy and on the machine where the policy is applied, you should see the registry keys added as below:

<b>HKLM\SOFTWARE\MICROSOFT\PolicyManager\AdmxInstalled</b>

<a href="https://msdnshared.blob.core.windows.net/media/2018/05/Pic-3.png"><img width="1905" height="464" class="alignnone wp-image-2725 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/05/Pic-3.png" /></a><b></b><i></i><u></u><sub></sub><sup></sup><span style="text-decoration: line-through"></span>

<b>HKLM\Software\Microsoft\PolicyManager\AdmxDefault</b>

<a href="https://msdnshared.blob.core.windows.net/media/2018/05/Pic21.png"><img width="956" height="797" class="alignnone wp-image-2775 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/05/Pic21.png" /></a><b></b><i></i><u></u><sub></sub><sup></sup><span style="text-decoration: line-through"></span>

&nbsp;
<h2><strong>Set the ShowHomeButton</strong></h2>
Now that we know the ADMX for Google Chrome has successfully been ingested on to the local machine, let’s look at how we can set the homepage URL.

In the ADMX, the section for the homepage is as below:

<em> &lt;policy class="Both" displayName="$(string.ShowHomeButton)" explainText="$(string.ShowHomeButton_Explain)" key="<strong>Software\Policies\Google\Chrome</strong>" name="<strong>ShowHomeButton</strong>" presentation="$(presentation.ShowHomeButton)" valueName="ShowHomeButton"&gt;</em>
<em>     &lt;parentCategory ref="<strong>googlechrome</strong>"/&gt;</em>
<em>     &lt;supportedOn ref="SUPPORTED_WINXPSP2"/&gt;</em>
<em>     &lt;enabledValue&gt;</em>
<em>          &lt;decimal value="1"/&gt;</em>
<em>     &lt;/enabledValue&gt;</em>
<em>     &lt;disabledValue&gt;</em>
<em>          &lt;decimal value="0"/&gt;</em>
<em>     &lt;/disabledValue&gt;</em>
<em> &lt;/policy&gt;</em>

To apply this policy via the OMA-URI we first need to build it. The <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><a href="https://docs.microsoft.com/en-us/windows/client-management/mdm/win32-and-centennial-app-policy-configuration">link</a></span> gives you an idea of how the policy can be built. Things to note from the ADMX snippet above are the registry key that would be set once the policy applies, the parent category and the value.

Within the ADMX, the parent category  for ShowHomeButton is googlechrome and is as below:

<em> &lt;category displayName="$(string.googlechrome)" name="googlechrome"&gt;
&lt;parentCategory ref="Google:Cat_Google"/&gt;
&lt;/category&gt;</em>

Using the guidelines in the articles above, I constructed the OMA-URI for the setting with the details below:
<pre>./Device/Vendor/MSFT/Policy/Config/GoogleChrome~Policy~googlechrome/ShowHomeButton</pre>
To explain that in parts
<ul>
 	<li>./Vendor/MSFT/Policy/Config/ remains same for all machine polices</li>
 	<li>GoogleChrome is your application name you chose on the ADMX ingestion URI.</li>
 	<li>ShowHomeButton is the actual category that we are setting in our example which translates to the reg key <i>Software\Policies\Google\Chrome\ShowHomeButton</i><span style="float: none;background-color: transparent;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;line-height: 24px;text-align: left;text-decoration: none;text-indent: 0px">.</span></li>
</ul>
There is an easier way to double check this if you have successfully ingested the ADMX to a machine. The below registry key would give you an idea of what it should look like.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\AdmxDefault\A8FC3654-6BCD-42AA-92BC-E1B20B96557B\GoogleChrome~Policy~googlechrome\ShowHomeButton

<a href="https://msdnshared.blob.core.windows.net/media/2018/06/ShowHomeReg.jpg"><img width="2517" height="617" class="alignnone wp-image-2845 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/06/ShowHomeReg.jpg" /></a>

The last part of the key is the OMA-URI that we are after. Note the ‘\’ need to replace with ‘/’ in the OMA-URI.The GUID <span style="float: none;background-color: transparent;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;line-height: 24px;text-align: left;text-decoration: none;text-indent: 0px">A8FC3654-6BCD-42AA-92BC-E1B20B96557B</span> will be specific to your machine.

Now that we have constructed the OMA URI we need to create the Windows 10 custom policy as shown below in Intune.

<a href="https://msdnshared.blob.core.windows.net/media/2018/06/PICCustomShowHomejpg.jpg"><img width="2040" height="694" class="alignnone wp-image-2875 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/06/PICCustomShowHomejpg.jpg" /></a>

The key values are:
<pre>OMA-URI:
./Device/Vendor/MSFT/Policy/Config/GoogleChrome~Policy~googlechrome/ShowHomeButton

Data Type:
 String

Value:
 <span style="margin: 0px;line-height: 107%;font-family: 'Calibri',sans-serif;font-size: 11pt"><span style="color: #000000">&lt;enabled/&gt;
</span></span></pre>
Save the policy and assign it to the same group that you assigned the admx policy to.  The data id is the text id of the category that you are setting from the ADMX which in our case is the HomepageLocation_Recommended.

After the machine has downloaded the policy, you should see the registry key as in the screenshot below:

<a href="https://msdnshared.blob.core.windows.net/media/2018/06/ShowHomeReg.png"><img width="2314" height="708" class="alignnone wp-image-2835 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/06/ShowHomeReg.png" /></a><b></b><i></i><u></u><sub></sub><sup></sup><span style="text-decoration: line-through"></span>

From a monitoring perspective, you should see the per-setting status to be a success as below:

<a href="https://msdnshared.blob.core.windows.net/media/2018/06/Monitor_Status_ShowHome.png"><img width="1965" height="714" class="alignnone wp-image-2865 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/06/Monitor_Status_ShowHome.png" /></a>

On the client, event ID 814 and 831 will be recorded for successful application of the policy as below:

<a href="https://msdnshared.blob.core.windows.net/media/2018/05/Pic-7.png"><img width="1380" height="613" class="alignnone wp-image-2765 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/05/Pic-7.png" /></a><b></b><i></i><u></u><sub></sub><sup></sup><span style="text-decoration: line-through"></span>

From a Chrome perspective, you should see the option enabled but the button is disabled and the setting is managed.

<a href="https://msdnshared.blob.core.windows.net/media/2018/06/ShowHomesetting.png"><img width="1164" height="109" class="alignnone wp-image-2855 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/06/ShowHomesetting.png" /></a>

Please note that if you are creating both the ADMX and the setting policy together it might take a couple of policy downloads before the policy applies to the device. The ADMX has to exist on the device before the policy setting can apply.

I hope this provides a good starting point to help you get started with managing policies for custom ADMX templates through Intune and better understanding of how to use Policy CSP on Window 10 latest versions for this purpose.]]></content:encoded>
    </item>
    <item>
      <title>Azure Cost Optimisation Series - Enable Hybrid Use Benefit (HUB) Using PowerShell</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/azure-cost-optimisation-series-enable-hybrid-use-benefit-hub-using-powershell</link>
      <pubDate>Wed, 07 Mar 2018 13:50:57 GMT</pubDate>
      <dc:creator><![CDATA[Neil Bird [Msft]]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2645</guid>
      <description><![CDATA[Hello all,

&nbsp;

My name is Neil Bird, I am a Premier Field Engineer (PFE) in the UK Cloud...]]></description>
      <content:encoded><![CDATA[Hello all,

&nbsp;

My name is Neil Bird, I am a Premier Field Engineer (PFE) in the UK Cloud &amp; Infrastructure team. I am passionate about helping customers get the most out of Microsoft Azure and Windows Server technologies. This is the second <a href="http://aka.ms/AzureIaaSCostOptimisation" rel="noopener" target="_blank">blog post in a series</a> where I will be sharing some information, scripts and approaches to help with Azure Cost Optimisation for IaaS.

&nbsp;
This week I want to highlight the massive savings customers can benefit from if they have purchased <strong>"Windows Server Datacentre with Software Assurance</strong>" licences for their on-premises workloads. Software Assurance (SA) licencing enables customers to deploy new (<em>or upgrade existing</em>) Windows Server instances using latest version of Windows Server. However, in addition to always being able to use the latest version of Windows Server Operating System, Software Assurance enables customers to make savings on Virtual Machine running costs in Azure. This makes Software Assurance even more compelling, as the savings can soon add up to huge amounts depending on the number of VM instances you have deployed in Azure.  

&nbsp;
Extract from: <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing" rel="noopener" target="_blank">Azure Hybrid Benefit for Windows Server</a>

&nbsp;
<blockquote><ol>
For customers with Software Assurance, Azure Hybrid Benefit for Windows Server allows you to use your on-premises Windows Server licenses and run Windows virtual machines on Azure at a reduced cost. You can use Azure Hybrid Benefit for Windows Server to deploy new virtual machines from any Azure supported platform Windows Server image or Windows custom images.</ol>
</blockquote>

&nbsp;

<strong>Azure Hybrid Use Benefit Eligibility:</strong>
If you would like to know if you / your organisation is eligible to enable Hybrid Use Benefit in Azure, you need to determine if you have purchased "Windows Server Datacenter with Software Assurance" licences for your on-premises environment and if so, the number of licences you hold. To find this out, you could ask your internal Software Asset Management team (<em>if applicable</em>) who could look in your organisation's Microsoft Volume Licence Service Centre (VLSC) portal. Alternatively, you could ask your Microsoft Software Reseller / Partner, or if you are a Microsoft Premier customer you could ask your Microsoft Technical Account Manager (TAM), who would be able to provide assistance.

&nbsp;
<strong>Windows Server Datacenter vs Standard Licences:</strong>
"Windows Server <strong>Datacenter</strong> with Software Assurance" allows customers to use the licence on-premises <strong>AND</strong> in Azure (<em>at the same time</em>).
"Windows Server <strong>Standard</strong> with Software Assurance" allows customers to use the licence on-premises <strong>OR</strong> in Azure (<em>Not at the same time</em>).

&nbsp;
<strong>Number of Windows Server Datacenter with SA Licences Held:</strong>
For each "Windows Server Datacenter with Software Assurance" licence held (<em>16 cores or a 2-Processor Licence</em>), you are eligible to deploy "Up to two VMs and up to 16 cores" with Hybrid Use Benefit (HUB) Enabled. For example: You can enable HUB on "two VMs with 8 vCPUs each, or one VM with 16 vCPUs" per "Windows Server Datacenter with Software Assurance" (16 cores or a 2-Processor) licence your organisation has purchased.

Or to put it another way: For each "16 Core or 2 Proc Windows Server Datacenter with Software Assurance Licence" you have already purchased for On-Premises, you can Enable Hybrid Use Benefit (HUB) on either "2 x 8 vCPU VMs" or "1 x 16 vCPU VM" in Azure. 

&nbsp;
I would like to point out that I am not a licensing specialist, if you have questions on the above or would like to understand more about Azure Hybrid Use Benefit there are a few official Microsoft articles that fully explain the Benefits, Rules and Use Cases for Azure Hybrid Use Benefit (AHUB). There is also a Pricing Calculator tool available. Links to these articles are below:
&nbsp;

<ol>
	<li><a href="https://docs.microsoft.com/en-gb/windows-server/get-started/azure-hybrid-benefit" rel="noopener" target="_blank">Getting Started with Azure Hybrid Use Benefit for Windows Server</a>
</li>
	<li><a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing" rel="noopener" target="_blank">Azure Hybrid Benefit for Windows Server</a>
</li>
	<li><a href="https://azure.microsoft.com/pricing/hybrid-use-benefit/" rel="noopener" target="_blank">Azure Hybrid Benefit for Windows Server licensing page</a>
</li>
	<li><a href="https://www.youtube.com/watch?v=5ZrC82PjnHI" rel="noopener" target="_blank">Microsoft Mechanics YouTube Video: How to deploy Azure VMs for less using the Azure Hybrid Benefit for Windows Server</a>
</li>
	<li><a href="https://support.microsoft.com/en-us/help/4036360/retirement-azure-hybrid-use-benefit-images-for-ea-subscriptions" rel="noopener" target="_blank">Retirement: Azure Hybrid Use Benefit images for EA subscriptions</a>
</li>
	<li><a href="https://azure.microsoft.com/en-us/overview/azure-for-microsoft-software/faq/" rel="noopener" target="_blank">Azure Hybrid Benefit for Windows Server FAQ</a>
</li>
</ol>

&nbsp;
<em><strong>Note</strong> - This information is correct at the time this blog post was published. I would always recommend and advise that customers review the latest official Microsoft documentation for any updated terms with regards to Azure HUB.</em>

&nbsp;
<strong>How do I Enable Hybrid Use Benefit on VMs in Azure?</strong> 
You may have been using Azure to deploy Windows VMs without realising your organisation is eligible to enable Azure Hybrid Use Benefit, this was the case for two customers I have been working with recently. After we determined the number of Windows Server Datacenter with Software Assurance licences they held, they both wanted to "Enable HUB on All Windows VMs in All of their Subscriptions" so that they could instantly start saving on the running cost of their IaaS VMs.

&nbsp;
To help my customers enable Hybrid Use Benefit on all of their existing Windows VM, in all of their subscriptions in Azure, I wrote a PowerShell script to automate the task and to provide additional audit / reporting information. The script had the following requirements / capabilities:

&nbsp;
<ol>
<li><strong>Include a "Simulate Mode" Option</strong>
(<em>This enabled them to audit their subscription(s) and understand what the script "would do" if they run it in update mode)</em>
<li><strong>Export information to a CSV File.</strong>
(<em>This includes VM Name, Resource Group, Size, HUB Enabled (Yes / No), Script Action and Subscription Name and ID</em>)
<li><strong>Provide a "Totals Count" showing the number VMs that have had HUB Enabled</strong>
(<em>This is useful for reporting on success / failure or VMs that are not compatible with HUB such as Linux VMs)</em>
<li><strong>Pass a "Subscription Name", if you do not want to process all subscriptions.</strong>
(<em>Useful if you have a large number of subscriptions.</em>)
<li><strong>Include a "Yes / No" confirmation prompt prior to enabling HUB on a per subscription basis</strong>
(<em>Useful if you want to update the majority of your subscriptions, but not all of them.)</em>
</ol>


&nbsp;

I wanted to share my PowerShell script with you, so that other customers can benefit from being able to Enable Hybrid Use Benefit on all existing Windows VMs in all of your Azure subscriptions, using a single PowerShell script.

&nbsp;
<strong>Example screenshot of the script in action</strong>:

<img src="https://msdnshared.blob.core.windows.net/media/2018/03/enable-hub-script-running.png" alt="" width="768" height="723" class="alignnone size-large wp-image-2635" />

&nbsp;
<strong>Example screenshot of the script results</strong>:

<img src="https://msdnshared.blob.core.windows.net/media/2018/03/enable-hub-script-results-1024x733.png" alt="" width="768" height="723" class="alignnone size-large wp-image-2655" />

&nbsp;
<strong>Dependencies</strong>:
The script requires: "AzureRM" Module v3.2 or above.
<ol>
<code>Install-Module AzureRM</code>
</ol>
&nbsp;
Or if you already have the "AzureRM" Module installed, check it is running the latest version:
<ol>
<code>Update-Module -Name AzureRM</code>
</ol>

&nbsp;

<strong>Example Syntax to Execute the Script</strong>:
Simulate Mode:
<ol>
    <code>.\azure-enable-hybrid-use-on-all-vms.ps1</code>
</ol>

Update Mode: (<em>The script displays a yes/no prompt per subscription prior to Enabling HUB on Windows VMs in that subscription</em>)
<ol>
    <code>.\azure-enable-hybrid-use-on-all-vms.ps1 -SimulateMode $False</code>
</ol>
&nbsp;

<strong>Output from the Script</strong>:
The script generates a CSV file called 'azure-enable-hybrid-use-audit-report.csv' which provides details of the VMs and either the "Action the Script Would Perform" (<em>Simulate Mode</em>), or the "Action the Script has Taken" (<em>Simulate Mode Disabled</em>).
&nbsp;

<strong>Download Link</strong>:

<a href="https://gallery.technet.microsoft.com/Azure-Enable-Hybrid-Use-1977e089" rel="noopener" target="_blank"><strong>Download the PowerShell Script from the TechNet Gallery</strong></a>

&nbsp;

<strong>Feedback</strong>: Please let me know if this PowerShell script is useful by posting a comment below.

&nbsp;

<strong>What about Enabling Hybrid Use Benefit for New VM Deployments?</strong>
The PowerShell script I have shared with you is designed to Enable HUB on Existing Windows VMs, that is, VMs that have already been deployed into an Azure subscription. For new VM deployments it is possible to enable HUB using ARM JSON or PowerShell so that newly deployed VMs have HUB enabled from their initial creation date. 
&nbsp;

<strong>Deploy a New VM with HUB Enabled using ARM JSON Parameter:</strong>
Within your Resource Manager templates, an additional parameter <code>licenseType</code> must be specified. Edit your Resource Manager template to include the "license type" as part of the compute provider and deploy your template as normal. Example below:

<code>"properties": {  
   <strong>"licenseType": "Windows_Server"</strong>,
   "hardwareProfile": {
        "vmSize": "[variables('vmSize')]"
   }</code>

&nbsp;
<strong>Deploy a New VM using PowerShell:</strong>
When deploying a VM in Azure using PowerShell, there is an additional parameter <code>-LicenseType</code> that can be specified. Example below using the final <code>New-AzureRmVM</code> cmd-let to set the licensing type:

Build VM Configuration as normal using this example for <a href="https://docs.microsoft.com/en-us/powershell/module/azurerm.compute/new-azurermvm?view=azurermps-6.1.0#examples" rel="noopener" target="_blank">New-AzureRmVMConfig</a>:

<code>$VM = New-AzureRmVMConfig.....</code>

<code>New-AzureRmVM -ResourceGroupName "myResourceGroup" -Location "West US" -VM $VM <strong>-LicenseType "Windows_Server"</strong></code>

&nbsp;

I hope you can use this PowerShell script to help Optimise the Cost of your Azure IaaS Deployments by enabling Hybrid Use Benefit on all existing Windows VMs deployed in your subscription(s).
&nbsp;

View other blog posts in the <a href="http://aka.ms/AzureIaaSCostOptimisation">Azure IaaS - Cost Optimisation Series</a>

<a href="http://aka.ms/AzureIaaSCostOptimisation">http://aka.ms/AzureIaaSCostOptimisation</a>

&nbsp;
Cheers
Neil

]]></content:encoded>
    </item>
    <item>
      <title>Azure Cost Optimisation Series - Identify Orphaned Disks Using PowerShell</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/azure-cost-optimisation-series-identify-orphaned-disks-using-powershell</link>
      <pubDate>Wed, 21 Feb 2018 12:57:07 GMT</pubDate>
      <dc:creator><![CDATA[Neil Bird [Msft]]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2625</guid>
      <description><![CDATA[Hello all,

&nbsp;

Neil Bird here again, I am a Premier Field Engineer (PFE) in the UK Cloud...]]></description>
      <content:encoded><![CDATA[Hello all,

&nbsp;

Neil Bird here again, I am a Premier Field Engineer (PFE) in the UK Cloud &amp; Infrastructure team. I am passionate about helping customers get the most out of Microsoft Azure and Windows Server technologies. This is the first in a <a href="http://aka.ms/AzureIaaSCostOptimisation" rel="noopener" target="_blank">series of blog posts</a> where I will be sharing some information, scripts and approaches to help with Azure Cost Optimisation for IaaS.

&nbsp;

Many (<em>if not all</em>) of the organisations I speak to have a strategy to migrate to public cloud in the near future, whether they are migrating their entire data centre or have a hybrid-cloud strategy, it is very easy for them to benefit from the increased Agility, Availability and Hyper-Scale offered by Microsoft Azure.

&nbsp;

Azure provides the ability for customers to be able to very quickly and easily expand (<em>scale horizontally</em>) or shrink services dynamically depending on their business requirements. This elasticity and Azure's "pay as you go" model are a key business enabler, providing organisation with the flexibility to react to market conditions, new opportunities or internal projects with lightning speed.

&nbsp;

For Infrastructure as a Service (IaaS) workloads, if you are spinning up (<em>deploying</em>) and tearing down (<em>deleting</em>) virtual machines (<em>VMs</em>) on a regular basis, there is a safety feature built into Azure whereby a VMs Disks are NOT automatically deleted when the VM itself is removed. This scenario (<em>NOT deleting the disks</em>) happens when a VM is deleted <strong>without</strong> deleting it's parent Resource Group, which is often the case if you have multiple VMs in the same Resource Group, possibly for RBAC delegation purposes.

&nbsp;

For example, if you were to select a single VM in the Azure Portal and delete it (<em>without deleting the Resource Group</em>), this would mean that the OS Disk, Data Disk(s) and Network Interface associated with that VM would NOT automatically be deleted, these Disks effectively become "<strong>Orphaned</strong>" and would still incur storage costs in your subscription(s).

&nbsp;

<blockquote><ol>
When you delete a virtual machine (VM) in Azure, by default, any disks that are attached to the VM aren't deleted. This feature helps to prevent data loss due to the unintentional deletion of VMs. After a VM is deleted, you will continue to pay for unattached disks.</ol>
</blockquote>

&nbsp;

This behaviour and some example PowerShell code to identify Orphaned Unmanaged and Managed Disks (<em>disks that are no longer associated with a VM</em>) are described in the following support article: <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/find-unattached-disks" rel="noopener" target="_blank">https://docs.microsoft.com/en-us/azure/virtual-machines/windows/find-unattached-disks</a>

&nbsp;

The sample PowerShell code included in the article above is very useful, however I received feedback from a couple of enterprise customers in the UK that it did NOT provide sufficient information for them to be able to make an informed decision as to whether it is safe to delete a disk or not. i.e - the "Disk URI" alone is not enough information.

&nbsp;

Following this feedback, I decided to create a more comprehensive PowerShell script that provides the following functionality:

&nbsp;
<ol>
<li><strong>Export data to a CSV File.</strong>
(<em>particularly useful if you have multiple subscriptions to audit, as the script appends audit data to the same file</em>)
<li><strong>Output additional properties of the Orphaned Disks.</strong>
(<em>such as "Disk Size", "Last Modified" and "Standard / Premium" Type and "Disk Space Used in GB" for Unmanaged Disks")</em>.
<li><strong>Ability to select the Target Azure Subscription from a list / GUI.</strong>
(<em>The subscription name can also be passed as a parameter if preferred</em>)
<li><strong>Integrate checking for both Managed and Unmanaged Disks into a single script.</strong>
<br>
<li><strong>If Delete parameter is set to $True, display an additional User Confirmation Prompt prior to deleting Orphaned Disks.</strong>
(<em>additional script safety mechanism</em>)
</ol>


&nbsp;

I wanted to share my PowerShell script with you, so that other customers can benefit from the additional disk information that can be exported when you audit your subscription(s) to <strong>Identify Orphaned Disks</strong>, the script can also Optionally Delete the disks that are identified; this is controlled using a script parameter that is off by default. 

&nbsp;
<strong>Example screenshot of the script in action</strong>:

<img src="https://msdnshared.blob.core.windows.net/media/2018/02/azure-get-orphaned-disks-screenshot-1024x964.png" alt="" width="768" height="723" class="alignnone size-large wp-image-2635" />

&nbsp;
<strong>Dependencies</strong>:

The script requires: "AzureRM" Module v3.2 or above.
<ol>
<code>Install-Module AzureRM</code>
</ol>

&nbsp;

<strong>Example Syntax to Execute the Script</strong>:

Audit Mode:
<ol>
    <code>.\azure-get-orphaned-disks.ps1</code>
</ol>

Audit and Optional Delete Mode: (<em>The script still displays a yes/no prompt to confirm deletion</em>)
<ol>
    <code>.\azure-get-orphaned-disks.ps1 -DeleteUnattachedDisks $True</code>
</ol>
&nbsp;


<strong>Excluding a Managed Disk</strong>: After reviewing the CSV Audit Report, if you would like to exclude one or more Managed Disks from being deleted, this is possible by creating a "Delete Resource Lock" on the Managed Disk(s). This might be relevant for scenarios such as <a href="https://azure.microsoft.com/en-gb/services/devtest-lab/" rel="noopener" target="_blank">Azure DevTest Labs</a> that could have Managed Data Disks that are not currently attached to a VM. Or you might have a Disk that is required but is not always attached to a VM. To create a "Delete Resource Lock" from the Azure Portal, find the Managed Disk --&gt; Settings --&gt; Locks --&gt; "Add". Give the Lock a Name and select the Lock type as "Delete", then "OK" to save. 

&nbsp;

<strong>Excluding an Entire Storage Account</strong>: After reviewing the CSV Audit Report, if you would like to exclude a specific Storage Account (<em>SA</em>) from being processed by the script, it is possible to temporarily create a "Read Only Lock" on the individual SA. This would prevent the deletion of All Unmanaged (<em>VHD</em>) Disks in that SA. This can be done from the Azure Portal, find the Storage Account --&gt; Settings --&gt; Locks --&gt; "Add". Give the Lock a Name and select the Lock type as "Read-only", then "OK" to save. After running the script, you could delete the Read-only Lock from the SA. <strong>Note</strong> - Please make sure you do not impact other services if you create a Read-only lock on a SA, you may need to arrange a maintenance window if applicable.

&nbsp;

<strong>Download Link</strong>:

<a href="https://gallery.technet.microsoft.com/Azure-Identify-and-baaf6a10" rel="noopener" target="_blank"><strong>Download the PowerShell Script from the TechNet Gallery</strong></a>

&nbsp;

The goal and purpose of this script is to enable you to make an informed decision about an Orphaned Disk in your subscription(s) before deleting it. However, please make sure you perform enough due diligence during your audit / analysis and that you have backups in place prior to deleting any disks.

&nbsp;

<strong>Feedback</strong>: Please let me know if this PowerShell script is useful by posting a comment below.

&nbsp;

I hope you can use it to help optimise the cost of your Azure subscription(s) by deleting orphaned disks that are no longer required.
&nbsp;

View other blog posts in the <a href="http://aka.ms/AzureIaaSCostOptimisation">Azure IaaS - Cost Optimisation Series</a>

<a href="http://aka.ms/AzureIaaSCostOptimisation">http://aka.ms/AzureIaaSCostOptimisation</a>

&nbsp;
Cheers
Neil

]]></content:encoded>
    </item>
    <item>
      <title>Awesomeness Made Easy With PowerBI</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/awesomeness-made-easy-with-powerbi</link>
      <pubDate>Tue, 12 Dec 2017 16:45:04 GMT</pubDate>
      <dc:creator><![CDATA[MS Dave Coles]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2455</guid>
      <description><![CDATA[Hi all,

My name is Dave Coles and I'm a Senior Premier Field Engineer based in the UK who...]]></description>
      <content:encoded><![CDATA[Hi all,

My name is Dave Coles and I'm a Senior Premier Field Engineer based in the UK who specializes in SCCM and Application Virtualization.

As we know, creating reports in SCCM using SSRB is a joyful experience second only to having root canal treatment at the Dentist.  Therefore when I heard the phrase, "Can I create a dashboard from that….. " from customers, I hid my inner pain and smiled knowing that I was about to lose hours creating the necessary outputs.  Recently, customers have been requesting a floor plan "Heat Map", I already know of a customer that uses this and can only assume word has spread.

However, having worked with PowerBI a lot recently, not only can I create this fiendish dashboard easily, but I can do so with a genuine smile (for those who've known me for a few years this is no mean achievement).  By following the basic information needed below; you can also achieve the same, smiling lesson excluded.

Items required (links to any downloads will be provided at point of reference).

1)      PowerBI , Desktop Edition is suitable.

2)      Synoptic Panel Add in by OKviz for PowerBI.

3)      A SVG editor,  online editor available at <a href="https://synoptic.design/">https://synoptic.design/</a>  for customers with internet access.

4)      A Data Source.

5)      A Floor Plan Image.

&nbsp;

For this example I am using a generic floor plan image obtained from Bing, which is contained within the Demo Offlice Plan Files Link below.

<a href="https://msdnshared.blob.core.windows.net/media/2017/12/Demo-Office-Plan.-Files.zip">Demo-Office-Plan.-Files</a>

<img width="300" height="244" class="alignnone size-medium wp-image-2465" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp1floorplan-300x244.jpg" />

&nbsp;

Additionally, for the data source, I created a test Excel Spreadsheet containing the following fields (Note: Name is my computer name).

<img width="835" height="179" class="alignnone size-full wp-image-2525" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/Excel.png" />

&nbsp;

First Step was to download and import the Synoptic Panel Add-in.

<a href="https://appsource.microsoft.com/en-us/product/power-bi-visuals/WA104380873?src=office&amp;tab=Overview">Synoptic Panel Download Link</a>

I needed to map the following Computers to areas (zones) on the image that PowerBI would recognise (shown in the next step).
<table width="185">
<tbody>
<tr>
<td width="68"><strong>HR1</strong></td>
<td width="68">HR</td>
</tr>
<tr>
<td width="68"><strong>HR2</strong></td>
<td width="68">HR</td>
</tr>
<tr>
<td width="68"><strong>FIN1</strong></td>
<td width="68">FINANCE</td>
</tr>
<tr>
<td width="68"><strong>FIN2</strong></td>
<td width="68">FINANCE</td>
</tr>
<tr>
<td width="68"><strong>PERS1</strong></td>
<td width="68">PERS</td>
</tr>
<tr>
<td width="68"><strong>PERS2</strong></td>
<td width="68">PERS</td>
</tr>
<tr>
<td width="68"><strong>MGR1</strong></td>
<td width="68">MGMT</td>
</tr>
<tr>
<td width="68"><strong>IT1</strong></td>
<td width="68">IT</td>
</tr>
<tr>
<td width="68"><strong>IT2</strong></td>
<td width="68">IT</td>
</tr>
<tr>
<td width="68"><strong>AV1</strong></td>
<td width="68">AV</td>
</tr>
<tr>
<td width="68"><strong>AV2</strong></td>
<td width="68">AV</td>
</tr>
<tr>
<td width="68"><strong>AV3</strong></td>
<td width="68">AV</td>
</tr>
<tr>
<td width="68"><strong>Proj1</strong></td>
<td width="68">Projects</td>
</tr>
<tr>
<td width="68"><strong>Proj2</strong></td>
<td width="68">Projects</td>
</tr>
<tr>
<td width="68"><strong>Proj3</strong></td>
<td width="68">Projects</td>
</tr>
<tr>
<td width="68"><strong>Proj4</strong></td>
<td width="68">Projects</td>
</tr>
<tr>
<td width="68"><strong>Rec1</strong></td>
<td width="68">Reception</td>
</tr>
</tbody>
</table>
&nbsp;

This is achieved by using a SVG editor to create named zones over the image, or put another way,  just use the SVG editor to draw squares over each desk and make sure each square is named after the respective computer located there.  Image below shows each location (desk) on the image where the corresponding area was created and named accordingly.  PowerBI will use the names to map the data to the image.

<img width="1024" height="621" class="alignnone size-large wp-image-2475" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp2floorplansvg-1024x621.jpg" />

The completed image was then saved as an SVG to a local folder.

&nbsp;

PowerBI was opened and the Synoptic Panel imported as per the following link.

<a href="https://store.office.com/powerbiaddininstallpage.aspx?rs=en-US&amp;assetid=WA104380873">How To Add a Plug in to PowerBI</a>

The Data Source was then added.

<img width="232" height="300" class="alignnone size-medium wp-image-2485" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp3adddata-232x300.jpg" />

&nbsp;

Selecting the Synoptic Panel add in places the object into the workspace.  Place and size according to your dashboard layout requirements.  The floor plan will not be available for import until data has been assigned to the add-in. Example image below shows synoptic panel with no data assigned.

<img width="1024" height="683" class="alignnone size-large wp-image-2486" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp4addin-1024x683.jpg" />

From the fields section the field:

<strong>Name</strong>  was dragged to the corresponding Category Data Area

<strong>Last AV Update</strong> to the Measure Data Area

<strong>Health State</strong> to the State Measure Area

&nbsp;

Now the data has been assigned the add in shows an additional tool bar from which the floor plan is imported.

<img width="300" height="171" class="alignnone size-medium wp-image-2495" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp5loadplan-300x171.jpg" />

&nbsp;

From the format section of the add in Data Labels were enabled and colour states applied for the Health Check settings.

<img width="1024" height="683" class="alignnone size-large wp-image-2496" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp6basicplan-1024x683.jpg" />

&nbsp;

The Floor Plan then reflected the current AV update Status.  To demonstrate a more comprehensive dashboard, the following standard components were added.

1)      A table showing last login time.

2)      A Pie Chart showing Health Status.

3)      A Pie Chart showing Device Guard Roll-out Status.

<img width="1024" height="612" class="alignnone size-large wp-image-2505" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/12/bp7Dashboard-1024x612.jpg" />

&nbsp;

SCCM provides a richer and more dynamic data source.  Integrate this with the intuituve interface and flexibilty of PowerBI and you have the ability to create dashboards with ease.  No more worries about inner or outer joins, data types or clunky interface.  Awesomeness at your fingertips!

So, create away …. and smile

&nbsp;

<strong>DISCLAIMER</strong>
The sample files are not supported under any Microsoft standard support program or service. The sample files 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 files and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the files 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.

<strong>David Coles | Senior Premier Field Engineer | SCCM, Application Virtualisation</strong>]]></content:encoded>
    </item>
    <item>
      <title>Azure Resource Manager (ARM) - Automate Installation of VM Extensions using PowerShell and JSON</title>
      <link>https://docs.microsoft.com/archive/blogs/ukplatforms/azure-resource-manager-arm-automate-installation-of-vm-extensions-using-powershell-and-json</link>
      <pubDate>Mon, 31 Jul 2017 14:44:44 GMT</pubDate>
      <dc:creator><![CDATA[Neil Bird [Msft]]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/ukplatforms/?p=2366</guid>
      <description><![CDATA[Hello all

&nbsp;

Neil Bird here from the Cloud &amp; Infrastructure team in the UK. I am a...]]></description>
      <content:encoded><![CDATA[Hello all

&nbsp;

Neil Bird here from the Cloud &amp; Infrastructure team in the UK. I am a Premier Field Engineer (PFE) specialising in helping customers get the most out of Microsoft Azure and Windows Server technologies.

&nbsp;

One of my customers recently asked me if there was an easy way to install the <a href="https://technet.microsoft.com/en-us/sysinternals/bginfo.aspx">BGInfo</a> Extension on all of the <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview">Azure Resource Manager</a> (ARM) VMs running in their Azure subscription. They had found a <a href="https://azure.microsoft.com/en-gb/blog/vm-agent-and-extensions-part-1/">previous Azure Blog on VM Extensions</a>, but this was from back in 2014 which meant the PowerShell cmdlets and example code was for Azure Service Manager (ASM), aka - "Classic VMs" and therefore is NOT valid for ARM VMs.

&nbsp;

<em>I hope everyone is using ARM for their Azure workloads, or if not, that you have plans to migrate from ASM to ARM in the near future. If you require more information on the difference between ASM and ARM, the following article provides useful information: <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-deployment-model">Azure Resource Manager vs. Classic Deployment: Understand deployment models and the state of your resources</a></em>

&nbsp;
<h4><span style="text-decoration: underline"><strong>Background:</strong></span></h4>
<a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/extensions-features">Azure virtual machine extensions</a> are small applications that provide post-deployment configuration and automation tasks on Azure virtual machines. For example, if a virtual machine requires software installation, anti-virus protection, or Docker configuration, a VM extension can be used to complete these tasks.

&nbsp;
<h4><span style="text-decoration: underline"><strong>Solution:</strong></span></h4>
The script framework I created and that I am sharing with you today is a PowerShell script that can be used to automate the installation of <strong>Any VM Extension</strong>. I have added lots of comments in <span style="color: #008000"><strong>green</strong></span> to make it easier to understand what actions the code is performing. I am aware that it is possible to install VM Extensions using a PowerShell "<a href="https://blogs.technet.microsoft.com/askpfeplat/2014/06/15/powershell-one-liners-to-get-you-started/">One-Liner</a>". However, I prefer to include prerequisite checks (<em>logic</em>) and an output showing the overall results as part of the installation process, this is useful when processing a large number of VMs and/or multiple subscriptions. The pre-install checks ensure the VMs are in the "correct state" to install an Extension, for example: in the BGInfo Extension scenario the checks <strong>ensure the following conditions are true</strong>:


&nbsp;
<ol>
 	<li>The VM <strong>has a Windows based OS installed</strong>, (<em>OS compatibility check</em>)</li>
 	<li>The VM<strong> is Running</strong>, required to Install Extensions</li>
 	<li>The VM does <strong>NOT already have the Extension Installed</strong></li>
</ol>
&nbsp;

If you use a PowerShell One-Liner to install VM Extensions, the installation could throw an error for any VM(s) that fail these tests. For example: if a VM is NOT running, it is NOT possible to install an Extension.

&nbsp;

The reason I have called the PowerShell script a "framework" is that it can be used to install any VM Extension. Although the original request was for BGInfo, I used Parameters for the "<strong>Extension Type</strong>" (<em>i.e. Name</em>) and "<strong>Publisher Name</strong>". This means you can easily install any other Extension using the same script, including Extensions that require additional "Settings". I will provide more information on how to specify Extension settings later in the post.

&nbsp;
<h4><span style="text-decoration: underline"><strong>Script Prerequisites:</strong></span></h4>
<ol>
 	<li>You need access to an <a href="https://azure.microsoft.com/en-gb/free/">Azure Subscription</a> that has one or more ARM VMs deployed in it.</li>
 	<li>The script must be executed from a device that has <a href="https://docs.microsoft.com/en-us/powershell/azure/install-azurerm-ps?view=azurermps-4.2.0">Azure PowerShell Installed</a>, i.e - the <strong>AzureRM.Compute v3.2 or above</strong> module.</li>
</ol>

&nbsp;
<u><strong> Updating AzureRM Module:</strong></u>
<code><span style="color: #008000"># Install the Azure Resource Manager modules from the PowerShell Gallery</span>
<strong>Install-Module AzureRM</strong></code>

&nbsp;

<h4><span style="text-decoration: underline"><strong>Script Parameters:</strong></span></h4>
To make the script simple to use and familiarise yourself with its use, I have left the Default Extension as <strong>BGInfo</strong>. This means if you would like to install the BGInfo Extension on the VMs running in your subscription, you only need to specify the "-<code><span style="color: #00ccff">SubscriptionName "&lt;Name of your subscription&gt;</span></code>" parameter (<em>or alternatively you could edit the parameter in the Params section of the code, </em><em>Line 144</em>).  This parameter is a string value that stores the name of the subscription you would like the script to process.

&nbsp;

Optionally also include the "-<code><span style="color: #00ccff">ProcessAllVMs</span></code>" switch when executing the script. This switch instructs the script to "process ALL of the VMs in the subscription". Note - If this is NOT specified the script will only process the first 3 x VMs in the subscription.

&nbsp;

For full details of the Script Parameters, including Syntax and a few Examples, once you have downloaded the script (<em>URL link below</em>), open a PowerShell prompt, change directory to the location you have saved the script to and run:
<p style="padding-left: 30px"><code><span style="color: #ff6600">get-help</span> .\azure-install-vm-extension.ps1 -full</code></p>
&nbsp;

The "<code>get-help -full"</code> command will output the Script Synopsis, Description, Parameters, Examples, Links and Notes.

&nbsp;

<h4><span style="text-decoration: underline"><strong>Download The Script:</strong></span></h4>
<strong>Recommended: <a href="https://gallery.technet.microsoft.com/scriptcenter/Automate-Installation-of-4f3b21f5" target="_blank">Download the script from The Microsoft TechNet Gallery</a></strong>.
&nbsp;

<h4><span style="text-decoration: underline"><strong>Sample Script:</strong></span></h4>
Without further ado, find below a sample PowerShell script that can be used to "<strong>Install an Extension on <span style="text-decoration: underline">ALL</span> Powered On, ARM VMs in a Subscription</strong>".

&nbsp;
<p style="padding-left: 30px"><span style="color: #ff0000"><strong>Important Note</strong></span> - The <strong>Warranty</strong> statement shown in the <code>.NOTES</code> section of the script is standard wording for "sample scripts". As with anything that makes changes to IT systems, it is <span style="text-decoration: underline">always</span> highly recommended to perform acceptance tests against a "non-production or test subscription" prior to implementing in a live / production subscription(s). In addition to following your organisation's Change Control / Release Management / Pipeline processes and procedures. (<em>Of course, you already know this, but I wanted to call this out.</em>)</p>


<p>&nbsp;</p>
Once you have <a href="https://gallery.technet.microsoft.com/scriptcenter/Automate-Installation-of-4f3b21f5" target="_blank">downloaded the script</a>, you can make changes or review the code in your favourite Script Editor, my "coding weapon of choice" is the awesome (<em>and free</em>) <a href="https://code.visualstudio.com/">Visual Studio Code</a> with the optional <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell">PowerShell Extension Installed</a> :-)
&nbsp;

The code from the script "<strong>azure-install-vm-extension.ps1</strong>" is shown below, so you can review it whilst reading this blog (<em>if you wish to</em>). To skip reviewing the code / continue reading the the blog, click <a href="#executing-the-script">here</a>.

<p>&nbsp;</p>


<div><p></p></div>
<div>
<div><code><span style="color: #008000">##########################################################################################################</span></code></div>
<div><code><span style="color: #008000">&lt;#</span></code></div>
<div><code><span>.SYNOPSIS</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Automates the installation of Extensions on VMs in an Azure Subscription.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Performs checks to ensure VM is in the correct state to install an extension.</span></code></div>
<div></div>
<div><code><span>.DESCRIPTION</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Script framework to install VM Extensions on All of the VMs in a subscription.</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">The Extension "Type" and "Publisher" are specified as parameters.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">If these parameters are NOT specified at execution, the script defaults to installing the "BGInfo" Extension on Windows VMs.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">The script performs the following checks to ensure a VM is in the "correct state" to install the extension:</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"> 1) VM is Running (NOT deallocated / stopped).</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000"> 2) VM is using an Operating System that is compatible with the Extension (configured as parameters).</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000"> 3) VM does not currently have the extension installed.</span></code></div>
<div><span><p></p></span></div>
<div><code>.PARAMETER SubscriptionName</code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Mandatory parameter. This can be configured in the script Params section or by passing as a pipeline parameter.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">The Name of the Azure Subscription you wish to process.</span></code></div>
<div><span><p></p></span></div>
<div><code>.PARAMETER VMExtensionName</code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Mandatory parameter with default of "BGInfo".</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span><span style="color: #008000"> The Name (Type) of the VM Extension you wish to install.</span></span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">This parameter is Case Sensitive, due to the comparison used to test if the Extension is already installed</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">The PowerShell code below can be used to obtain a full list of the VM Extensions that are available in an Azure region.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Edit the $location variable with your target region</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">[string]$location = "uksouth"</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Get-AzureRmVmImagePublisher -Location $location | `</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Get-AzureRmVMExtensionImageType | `</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Get-AzureRmVMExtensionImage | Select Type, PublisherName | ft *</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.PARAMETER VMExtensionPublisher</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Mandatory parameter with default of "Microsoft.Compute".</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">The Publisher of the VM Extension you wish to install.</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.PARAMETER ProcessAllVMs</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Include this parameter if you would like to "Process ALL VMs in the subscription".</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">If this parameter is NOT included the script will only process the first 3 x VMs in the subscription.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">This is a safety measure to prevent unintentionally installing the Extension on ALL VMs.</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.LINK</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">https://blogs.technet.microsoft.com/ukplatforms/2017/07/31/azure-resource-manager-arm-automate-installation-of-vm-extensions-using-powershell-and-json</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.EXAMPLE</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">.\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise"</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Installs the default VM Extension configured in the script parameters (BGInfo) on first 3 x Windows VMs in the "Visual Studio Enterprise" subscription.</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.EXAMPLE</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">.\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise" -ProcessAllVMs</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Installs the default VM Extension configured in the script parameters (BGInfo) on ALL of the Windows VMs in the "Visual Studio Enterprise" subscription.</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.EXAMPLE</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">.\azure-install-vm-extension.ps1 -SubscriptionName "Visual Studio Enterprise" `</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 60px"><code><span style="color: #008000">-VMExtensionName "IaaSAntimalware" `</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #008000">-VMExtensionPublisher "Microsoft.Azure.Security" `</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #008000">-VMExtensionWindowsCompatible $true `</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #008000">-VMExtensionLinuxCompatible $false `</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #008000">-VMExtensionSettingsFilePath "C:\scripts\IaaSAntimalware-Config.json" `</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #008000">-ProcessAllVMs</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Installs the "IaaSAntimalware" VM Extension on ALL of the Windows VMs in the "Visual Studio Enterprise" subscription.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Configures the "IaaSAntimalware" Extension settings using the configuration in the "C:\scripts\IaaSAntimalware-Config.json" file.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">Example JSON Schema for "Microsoft Antimalware" Extension, "C:\scripts\IaaSAntimalware-Config.json" file:</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 60px"><code><span style="color: #008000">{</span></code></div>
<div style="padding-left: 90px"><code><span style="color: #008000">"AntimalwareEnabled": true,</span></code></div>
<div style="padding-left: 90px"><code><span style="color: #008000">"RealtimeProtectionEnabled": true,</span></code></div>
<div style="padding-left: 90px"><code><span style="color: #008000">"ScheduledScanSettings": {</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"isEnabled": true,</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"day": "7",</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"time": "120",</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"scanType": "Quick"</span></code></div>
<div style="padding-left: 90px"><code><span style="color: #008000">},</span></code></div>
<div style="padding-left: 90px"><code><span style="color: #008000">"Exclusions": {</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"Extensions": "",</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"Paths": "%windir%\\SoftwareDistribution\\Datastore\\DataStore.edb;%windir%\\SoftwareDistribution\\Datastore\\Logs\\Edb.chk",</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000">"Processes": ""</span></code></div>
<div style="padding-left: 90px"><code><span style="color: #008000">}</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #008000">}</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">For full details on how to configure the "Microsoft Antimalware Extension" settings including file exclusions, see the following article:</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">"Microsoft Antimalware for Azure Cloud Services and Virtual Machines"</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">(https://docs.microsoft.com/enus/azure/security/azure-security-antimalware)</span></code></div>
<div><span><p></p></span></div>
<div><code><span>.NOTES</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">THIS CODE-SAMPLE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED </span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR </span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">FITNESS FOR A PARTICULAR PURPOSE.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">This sample is not supported under any Microsoft standard support program or service.</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">The script is provided AS IS without warranty of any kind. Microsoft further disclaims all</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">implied warranties including, without limitation, any implied warranties of merchantability</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">or of fitness for a particular purpose. The entire risk arising out of the use or performance</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">of the sample and documentation remains with you. In no event shall Microsoft, its authors,</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">or anyone else involved in the creation, production, or delivery of the script be liable for</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">any damages whatsoever (including, without limitation, damages for loss of business profits,</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">business interruption, loss of business information, or other pecuniary loss) arising out of</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">the use of or inability to use the sample or documentation, even if Microsoft has been advised</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">of the possibility of such damages, rising out of the use of or inability to use the sample script,</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">even if Microsoft has been advised of the possibility of such damages.</span></code></div>
<div><p></p><div>
<div><code><span style="color: #008000">#&gt;</span></code></div>
<div><code><span style="color: #008000">##########################################################################################################</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000">###############################</span></code></div>
<div><code><span style="color: #008000">## SCRIPT OPTIONS &amp; PARAMETERS</span></code></div>
<div><code><span style="color: #008000">###############################</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000">#Requires -Version 3</span></code></div>
<div><code><span style="color: #008000">#Requires -Modules AzureRM</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Version: 1.1</span></code></div>
<div><code><span style="color: #008000">&lt;# - 28/07/2017</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">* added progress bar and confirmation prompt.</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">* added "-ProcessAllVMs" switch, without this script only processes 3 x VMs by default.</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">* added parameter to specify a "SettingString" configuration file for Extension settings.</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">* added counters to provide an "installation results" report when the script completes.</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000">- 14/07/2017</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000">* initial script creation.</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000">#&gt;</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Define and validate mandatory parameters</span></code></div>
<div><code><span style="color: #0000ff">[CmdletBinding<span style="color: #00ccff">()</span>]</span></code></div>
<div><code><span style="color: #ff00ff">Param<span style="color: #00ccff">(</span></span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Azure Subscription Name</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=1)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">SubscriptionName = <span style="color: #ff6600">"SUBSCRIPTION NAME"</span>,</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># ***** EDIT ABOVE WITH YOUR SUBSCRIPTION NAME, OR PASS AS SCRIPT PARAMETER *****</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># VM Extension Name (Case sensitive for "Extensions.id.Contains" comparison)</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=2)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[string]<span style="color: #0000ff">$</span>VMExtensionName = <span style="color: #ff6600">"BGInfo"</span>,</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># VM Extension Publisher</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=3)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[string]<span style="color: #0000ff">$</span>VMExtensionPublisher = <span style="color: #ff6600">"Microsoft.Compute"</span>,</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># VM Extension Windows OS Compatible</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=4)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[bool]<span style="color: #0000ff">$</span>VMExtensionWindowsCompatible = <span style="color: #0000ff">$true</span>,</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># VM Extension Linux OS Compatible</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=5)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[bool]<span style="color: #0000ff">$</span>VMExtensionLinuxCompatible = <span style="color: #0000ff">$false</span>,</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># VM Extension JSON Settings File Path</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=6)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[string]<span style="color: #0000ff">$</span>VMExtensionSettingsFilePath = <span style="color: #ff6600">""</span>,</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Process All VMs in Subscription Switch, if not present script only processes first 3 VMs</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">[parameter<span style="color: #00ccff">(</span>Position<span style="color: #00ccff">=7)</span>]</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">[switch]<span style="color: #0000ff">$</span>ProcessAllVMs</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #00ccff">)</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Set strict mode to identify typographical errors</span></code></div>
<div><code><span><span style="color: #ff6600">Set-StrictMode</span> -Version Latest</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Make the script verbose by default</span></code></div>
<div><code><span style="color: #0000ff">$VerbosePreference = <span style="color: #ff6600">"Continue"</span></span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000">##########################################################################################################</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000">#####################################</span></code></div>
<div><code><span style="color: #008000">## FUNCTION 1 - Install-VMExtension</span></code></div>
<div><code><span style="color: #008000">#####################################</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #0000ff">Function </span><span>Install-VMExtension {</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Connect to Azure</span></code></div>
<div style="padding-left: 30px"><code><span>Write-Host </span><span style="color: #ff6600">"`nPrompting for Azure Credentials and Authenticating..."</span></code></div>
<div style="padding-left: 30px"><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Login to Azure Resource Manager (ARM), if this fails, stop script.</span></code></div>
<div style="padding-left: 30px"><code><span>Login-AzureRmAccount -SubscriptionName <span style="color: #0000ff">$</span><span style="color: #00ccff">SubscriptionName</span> -ErrorAction Stop</span></code></div>
<div style="padding-left: 30px"><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Get all ARM VMs in Subscription</span></code></div>
<div style="padding-left: 30px"><code><span><span style="color: #00ccff">[array]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMs</span> = Get-AzureRMVM -Status -ErrorAction Stop</span></code></div>
<div style="padding-left: 30px"><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Counter for Progress bar and $ProcessAllVMs switch</span></code></div>
<div style="padding-left: 30px"><code><span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMsProcessed</span> = 0</span></code></div>
<div style="padding-left: 30px"><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Loop through all VMs in the Subscription</span></code></div>
<div style="padding-left: 30px"><code><span><span style="color: #ff00ff">ForEach</span> <span style="color: #0000ff">($</span><span style="color: #00ccff">VM</span> <span style="color: #ff00ff">in</span> <span style="color: #0000ff">$</span><span style="color: #00ccff">VMs</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div style="padding-left: 60px"><span><p></p></span></div>
<div style="padding-left: 60px"><code><span style="color: #008000"># Check if the ProcessAllVMs switch has NOT been set</span></code></div>
<div style="padding-left: 60px"><code><span><span style="color: #ff00ff">if</span><span style="color: #0000ff">(</span><span>!</span><span style="color: #0000ff">$</span><span style="color: #00ccff">ProcessAllVMs</span><span>.IsPresent</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div style="padding-left: 60px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span style="color: #008000"># We are NOT Processing All VMs (switch NOT present), stop after first 3 x VMs </span></code></div>
<div style="padding-left: 90px"><code><span><span style="color: #ff00ff">if</span><span style="color: #0000ff">(</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMsProcessed</span><span> </span><span>-eq</span><span> </span><span>3</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># Write informational message about use of the -ProcessAllVMs switch</span></code></div>
<div style="padding-left: 120px"><code><span> Write-Host </span><span style="color: #ff6600">"`nINFO: Script Stopping."</span></code></div>
<div style="padding-left: 120px"><code><span> </span><span>Write-Host</span><span> </span><span>'INFO: To process more than the first 3 x VMs in a subscription, Set the -ProcessAllVMs parameter when executing the script.'</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># Break out of the ForEach Loop to stop processing</span></code></div>
<div style="padding-left: 120px"><code><span style="color: #ff00ff">Break</span></code></div>
<div style="padding-left: 90px"><code><span> }</span></code></div>
<div style="padding-left: 60px"><code><span> }</span></code></div>
<div style="padding-left: 60px"><span><p></p></span></div>
<div style="padding-left: 60px"><code><span style="color: #008000"># Show the Progress bar for number of VMs Processed...</span></code></div>
<div style="padding-left: 60px"><code><span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMsProcessed</span>++</span></code></div>
<div style="padding-left: 60px"><code><span>Write-Progress -Activity <span style="color: #ff6600">"Processing VMs in ""</span><span style="color: #0000ff">$($</span><span style="color: #00ccff">SubscriptionName</span><span style="color: #0000ff">)</span><span style="color: #ff6600">&gt;""..."</span><span> `</span></code></div>
<div style="padding-left: 60px"><code><span>-Status <span style="color: #ff6600">"Processed: </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMsProcessed</span><span style="color: #ff6600"> of </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMs</span><span>.count</span><span style="color: #0000ff">)</span><span>"<span> `</span></code></div>
<div style="padding-left: 60px"><code><span>-PercentComplete </span><span style="color: #0000ff">(($</span><span style="color: #00ccff">VMsProcessed</span><span> / </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMs</span><span>.Count</span><span style="color: #0000ff">)</span><span>*</span><span>100</span><span style="color: #0000ff">)</span></code></div>
<div style="padding-left: 60px"><span><p></p></span></div>
<div style="padding-left: 60px"><code><span style="color: #008000"># Ensure the VM OS is Compatible with Extension</span></code></div>
<div style="padding-left: 60px"><code><span style="color: #ff00ff">if</span><span style="color: #0000ff">(($</span><span style="color: #00ccff">VM</span><span>.OSProfile.WindowsConfiguration -and </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionWindowsCompatible</span><span style="color: #0000ff">)</span><span> `</span></code></div>
<div style="padding-left: 60px"><code><span> -or </span><span style="color: #0000ff">($</span><span style="color: #00ccff">VM</span><span>.OSProfile.LinuxConfiguration -and </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionLinuxCompatible</span><span style="color: #0000ff">))</span><span> {</span></code></div>
<div style="padding-left: 90px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span style="color: #008000"># Ensure the Extension is NOT already installed</span></code></div>
<div style="padding-left: 90px"><code><span><span style="color: #ff00ff">if</span><span style="color: #0000ff">(($</span><span style="color: #00ccff">VM</span><span>.Extensions.count -eq 0</span><span style="color: #0000ff">)</span><span> -or </span><span style="color: #0000ff">(</span><span>!</span><span style="color: #0000ff">(</span><span>Split-Path -Leaf </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.Extensions.id</span><span style="color: #0000ff">)</span><span>.Contains</span><span style="color: #0000ff">($</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)))</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># If VM is Running</span></code></div>
<div style="padding-left: 120px"><code><span><span style="color: #ff00ff">if</span><span style="color: #0000ff">(</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.PowerState -eq </span><span style="color: #ff6600">'VM running'</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div style="padding-left: 150px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># Output the VM Name</span></code></div>
<div style="padding-left: 150px"><code><span> Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span>: requires </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)</span><span>, installing..."</span></code></div>
<div style="padding-left: 150px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># Get the latest version of the Extension in the VM's Location:</span></code></div>
<div style="padding-left: 150px"><code><span style="color: #00ccff">[version]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionVersion</span><span> = </span><span style="color: #0000ff">(</span><span>Get-AzureRmVMExtensionImage -Location </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.Location</span><span> `</span></code></div>
<div style="padding-left: 150px"><code><span>-PublisherName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionPublisher</span><span> -Type </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)</span><span>.Version `</span></code></div>
<div style="padding-left: 150px"><code><span> | </span><span style="color: #ff00ff">ForEach-Object</span><span> { </span><span>New-Object System.Version </span><span style="color: #0000ff">($</span><span style="color: #00ccff">PSItem</span><span style="color: #0000ff">)</span><span>} | `</span></code></div>
<div style="padding-left: 150px"><code><span> Sort-Object -Descending | Select-Object -First 1</span></code></div>
<div style="padding-left: 150px"><code><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionVersionMajorMinor</span><span> = </span><span style="color: #ff6600">"{0}.{1}"</span><span> -F </span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionVersion</span><span>.Major,</span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionVersion</span><span>.Minor</span></code></div>
<div style="padding-left: 150px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># If the $VMExtensionSettingFilePath parameter has been specified and the file exists</span></code></div>
<div style="padding-left: 150px"><code><span style="color: #ff00ff">if</span><span style="color: #0000ff">(($</span><span style="color: #00ccff">VMExtensionSettingsFilePath</span><span> -ne </span><span style="color: #ff6600">""</span><span style="color: #0000ff">)</span><span> -and </span><span style="color: #0000ff">(</span><span>Test-Path </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionSettingsFilePath</span><span style="color: #0000ff">))</span><span> {</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 180px"><code><span style="color: #008000"># Import Extension Config File</span></code></div>
<div style="padding-left: 180px"><code><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionConfigfile</span><span> = Get-Content </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionSettingsFilePath</span><span> -Raw</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 180px"><code><span style="color: #008000"># Install the Extension with SettingString parameter</span></code></div>
<div style="padding-left: 180px"><code><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionInstallResult</span><span> = Set-AzureRmVMExtension -ExtensionName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionName</span><span> `</span></code></div>
<div style="padding-left: 180px"><code><span> -Publisher </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionPublisher</span><span> -TypeHandlerVersion </span><span style="color: #0000ff">$</span><span>ExtensionVersionMajorMinor</span><span> </span><span>-</span><span>ExtensionType </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionName</span><span> `</span></code></div>
<div style="padding-left: 180px"><code><span> -Location </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.Location -ResourceGroupName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.ResourceGroupName</span><span> `</span></code></div>
<div style="padding-left: 180px"><code><span> -SettingString </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionConfigfile</span><span> -VMName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.Name</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span> } </span><span><span style="color: #ff00ff">else</span><span> {</span><span style="color: #008000"> # $VMExtensionSettingFilePath does NOT exist</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 180px"><code><span style="color: #008000"># Install the Extension WITHOUT SettingString parameter</span></code></div>
<div style="padding-left: 180px"><code><span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionInstallResult</span><span> = Set-AzureRmVMExtension -ExtensionName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionName</span><span> `</span></code></div>
<div style="padding-left: 180px"><code><span> -Publisher </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionPublisher</span><span> -TypeHandlerVersion </span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionVersionMajorMinor</span><span> -ExtensionType </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionName</span><span> `</span></code></div>
<div style="padding-left: 180px"><code><span> -Location </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.Location -ResourceGroupName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.ResourceGroupName</span><span> `</span></code></div>
<div style="padding-left: 180px"><code><span> -VMName </span><span style="color: #0000ff">$</span><span style="color: #00ccff">VM</span><span>.Name</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span>}</span><span style="color: #008000"> # Install Extension with SettingString parameter if file specified and exists</span></code></div>
<div style="padding-left: 150px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># Installation finished, check the return status code</span></code></div>
<div style="padding-left: 150px"><code><span><span style="color: #ff00ff">if</span><span style="color: #0000ff">(</span><span style="color: #0000ff">$</span><span style="color: #00ccff">ExtensionInstallResult</span><span>.IsSuccessStatusCode -eq </span><span style="color: #0000ff">$true)</span><span> {</span></span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 180px"><code><span style="color: #008000"># Installation Succeeded</span></code></div>
<div style="padding-left: 180px"><code><span> Write-Host </span><span style="color: #ff6600">"SUCCESS: "</span><span> -ForegroundColor Green -nonewline; `</span></code></div>
<div style="padding-left: 180px"><code><span> Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Extension installed successfully"</span></code></div>
<div style="padding-left: 180px"><code> <span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">SuccessCount</span><span>++</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span> } </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div style="padding-left: 180px"><span><p></p></span></div>
<div style="padding-left: 180px"><code><span style="color: #008000"># Installation Failed</span></code></div>
<div style="padding-left: 180px"><code><span> Write-Host </span><span style="color: #ff6600">"ERROR: "</span><span> -ForegroundColor Red -nonewline; `</span></code></div>
<div style="padding-left: 180px"><code><span> Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Failed - Status Code: </span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">ExtensionInstallResult</span><span>.StatusCode</span><span style="color: #0000ff">)</span><span>"</span></code></div>
<div style="padding-left: 180px"><code> <span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">FailedCount</span><span>++</span></code></div>
<div style="padding-left: 150px"><code><span> }</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span> } </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># VM is NOT Running</span></code></div>
<div style="padding-left: 150px"><code><span> Write-Host </span><span style="color: #ff6600">"WARN: "</span><span> -ForegroundColor Yellow -nonewline; `</span></code></div>
<div style="padding-left: 150px"><code><span> Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Unable to install </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> - VM is NOT Running"</span></code></div>
<div style="padding-left: 150px"><code> <span style="color: #0000ff">$Global</span><span>:</span><span>VMsNotRunningCount</span><span>++</span></code></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># Could use "Start-AzureRmVM -ResourceGroupName $vm.ResourceGroupName -Name $VM.Name",</span></code></div>
<div style="padding-left: 150px"><code><span style="color: #008000"># wait for VM to start and Install extension, possible improvement for future version.</span></code></div>
<div style="padding-left: 150px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span> }</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span> } </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># VM already has the Extension installed.</span></code></div>
<div style="padding-left: 120px"><code><span> Write-Host </span><span style="color: #ff6600">"INFO: </span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Already has the </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> Extension Installed"</span></code></div>
<div style="padding-left: 120px"><code> <span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">AlreadyInstalledCount</span><span>++</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span> }</span></code></div>
<div style="padding-left: 90px"><span><p></p></span></div>
<div style="padding-left: 60px"><code><span style="color: #008000"># Extension NOT Compatible with VM OS, as defined in Script Parameters boolean values</span></code></div>
<div style="padding-left: 60px"><code><span> } </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div style="padding-left: 60px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span style="color: #008000"># Linux</span></code></div>
<div style="padding-left: 90px"><code><span><span style="color: #ff00ff">if</span><span style="color: #0000ff">($</span><span style="color: #00ccff"></span><span style="color: #00ccff">VM</span><span>.OSProfile.LinuxConfiguration</span><span> -and </span><span style="color: #0000ff">(</span><span>!</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionLinuxCompatible</span><span style="color: #0000ff">))</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># VM is running Linux distro and $VMExtensionLinuxCompatible = $false</span></code></div>
<div style="padding-left: 120px"><code><span> Write-Host </span><span style="color: #ff6600">"INFO: </span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Is running a Linux OS, extension </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> is not compatible, skipping..."</span></code></div>
<div style="padding-left: 120px"><code> <span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">OSNotCompatibleCount</span><span>++</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span style="color: #008000"># Windows   </span></code></div>
<div style="padding-left: 90px"><code><span> } </span><span><span style="color: #ff00ff">elseif</span><span> </span><span style="color: #0000ff">($</span><span style="color: #00ccff">VM</span><span>.OSProfile.WindowsConfiguration</span><span> -and </span><span style="color: #0000ff">(</span><span>!</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMExtensionWindowsCompatible</span><span style="color: #0000ff">))</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># VM is running Windows $VMExtensionWindowsCompatible = $false</span></code></div>
<div style="padding-left: 120px"><code><span> Write-Host </span><span style="color: #ff6600">"INFO: </span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Is running a Windows OS, extension </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span>)</span><span style="color: #ff6600"> is not compatible, skipping..."</span></code></div>
<div style="padding-left: 120px"><code> <span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">OSNotCompatibleCount</span><span>++</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span style="color: #008000"># Error VM does NOT have a Windows or Linux Configuration</span></code></div>
<div style="padding-left: 90px"><code><span> } </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 120px"><code><span style="color: #008000"># Unexpected condition, VM does not have a Windows or Linux Configuration</span></code></div>
<div style="padding-left: 120px"><code><span> Write-Host </span><span style="color: #ff6600">"ERROR: "</span><span> -ForegroundColor Red -nonewline; `</span></code></div>
<div style="padding-left: 120px"><code><span> Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VM</span><span>.Name</span><span style="color: #0000ff">)</span><span style="color: #ff6600">: Does NOT have a Windows or Linux OSProfile!?"</span></code></div>
<div style="padding-left: 120px"><span><p></p></span></div>
<div style="padding-left: 90px"><code><span> }</span><span style="color: #008000"> # Extension OS Compatibility</span></code></div>
<div style="padding-left: 90px"><span><p></p></span></div>
<div style="padding-left: 60px"><code><span> }</span><span style="color: #008000"> # ForEach VM Loop</span></code></div>
<div style="padding-left: 60px"><span><p></p></span></div>
<div style="padding-left: 30px"><code><span> }</span><span style="color: #008000"> # end of Function Install-VMExtension</span></code></div>
<div style="padding-left: 30px"><span><p></p></span></div>
<div><code><span>}</span><span style="color: #008000"> # end of Function Install-VMExtension</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Setup counters for Extension installation results</span></code></div>
<div><code><span style="color: #00ccff">[double]</span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">SuccessCount</span><span> </span> = </span><span>0</span></code></div>
<div><code><span style="color: #00ccff">[double]</span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">FailedCount</span><span> </span> = </span><span>0</span></code></div>
<div><code><span style="color: #00ccff">[double]</span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">AlreadyInstalledCount</span><span> </span> = </span><span>0</span></code></div>
<div><code><span style="color: #00ccff">[double]</span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">VMsNotRunningCount</span><span> </span> = </span><span>0</span></code></div>
<div><code><span style="color: #00ccff">[double]</span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">OSNotCompatibleCount</span><span> </span> = </span><span>0</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">DateTimeNow</span><span> = get-date -Format </span><span style="color: #ff6600">"dd/MM/yyyy - HH:mm:ss"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"`n========================================================================`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span><span style="color: #00ccff">DateTimeNow</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> - Install VM Extension Script Starting...`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"========================================================================`n"</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Prompt for confirmation...</span></code></div>
<div><code><span style="color: #ff00ff">if</span><span style="color: #0000ff">($</span><span style="color: #00ccff">ProcessAllVMs</span><span>.IsPresent</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div style="padding-left: 30px"><code><span> </span><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMTargetCount</span><span> </span> = </span><span style="color: #ff6600">"ALL of the"</span></code></div>
<div><code><span>} </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div style="padding-left: 30px"><code><span> </span><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">VMTargetCount</span><span> </span> = </span><span style="color: #ff6600"> "the first 3 x"</span></code></div>
<div><code><span>}</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># User prompt confirmation before processing</span></code></div>
<div><code><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">UserPromptMessage</span><span> </span> = </span><span style="color: #ff6600">"Do you want to install the ""</span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span>)</span><span style="color: #ff6600">"" Extension on </span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">VMTargetCount</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> VMs in the ""</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">SubscriptionName</span><span style="color: #0000ff">)</span><span style="color: #ff6600">"" Subscription?"</span></code></div>
<div><code><span style="color: #ff00ff">if</span><span style="color: #0000ff">(</span><span>!</span><span style="color: #0000ff">$</span><span style="color: #00ccff">ProcessAllVMs</span><span>.IsPresent</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div style="padding-left: 30px"><code><span style="color: #0000ff">$</span><span style="color: #00ccff">UserPromptMessage</span><span> = </span><span style="color: #0000ff">$</span><span style="color: #00ccff">UserPromptMessage</span><span> + </span><span style="color: #ff6600">"`n`nNote: use the ""-ProcessAllVMs"" switch to install the Extension on ALL VMs."</span></code></div>
<div><code><span>}</span></code></div>
<div><code><span style="color: #0000ff">$</span><span style="color: #00ccff">UserPromptMessage</span><span> = </span><span style="color: #0000ff">$</span><span style="color: #00ccff">UserPromptMessage</span><span> + </span><span style="color: #ff6600">"`n`nType ""yes"" to confirm....`n`n`t"</span></code></div>
<div><code><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">UserConfirmation</span><span> </span> = </span><span>Read-Host</span><span> -Prompt </span><span style="color: #0000ff">$</span><span style="color: #00ccff">UserPromptMessage</span></code></div>
<div><code><span style="color: #ff00ff">if</span><span style="color: #0000ff">($</span><span style="color: #00ccff">UserConfirmation</span><span>.ToLower</span><span>()</span><span> -ne </span><span style="color: #ff6600">'yes'</span><span style="color: #0000ff">)</span><span> {</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Abort script, user reponse was NOT "yes"</span></code></div>
<div style="padding-left: 30px"><code><span> Write-Host </span><span style="color: #ff6600">"`nUser typed ""</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">UserConfirmation</span><span style="color: #0000ff">)</span><span style="color: #ff6600">"", Aborting script...`n`n"</span><span> -ForegroundColor Red</span></code></div>
<div style="padding-left: 30px"><code><span>Exit</span></code></div>
<div><span><p></p></span></div>
<div><code><span>} </span><span><span style="color: #ff00ff">else</span><span> {</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Continue, user responded "yes" to confirm</span></code></div>
<div style="padding-left: 30px"><code><span> Write-Host </span><span style="color: #ff6600">"`nUser typed 'yes' to confirm...."</span><span> </span><span>-</span><span>ForegroundColor Green</span></code></div>
<div style="padding-left: 30px"><code><span> Write-Host </span><span style="color: #ff6600">"Processing...`n"</span></code></div>
<div><span><p></p></span></div>
<div style="padding-left: 30px"><code><span style="color: #008000"># Call Function to Install Extension on VMs</span></code></div>
<div style="padding-left: 30px"><code><span>Install-VMExtension</span></code></div>
<div><span><p></p></span></div>
<div><code><span>}</span></code></div>
<div><code><span style="color: #008000"># Add up all of the counters</span></code></div>
<div><code><span style="color: #00ccff">[double]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">TotalVMsProcessed</span><span> = </span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">SuccessCount</span><span> + </span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">FailedCount</span><span> + </span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">AlreadyInstalledCount</span><span> `</span></code></div>
<div><code><span> + </span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">VMsNotRunningCount</span><span> + </span><span style="color: #0000ff">$Global</span><span>:</span><span style="color: #00ccff">OSNotCompatibleCount</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #008000"># Output Extension Installation Results</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"========================================================================"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"`tExtension </span><span style="color: #0000ff">$($</span><span style="color: #00ccff">VMExtensionName</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> - Installation Results`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"Installation Successful:`t`t</span><span style="color: #0000ff">$($Global</span><span>:</span><span style="color: #00ccff">SuccessCount</span><span style="color: #0000ff">)</span><span>"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"Already Installed:`t`t`t</span><span style="color: #0000ff">$($Global</span><span>:</span><span style="color: #00ccff">AlreadyInstalledCount</span><span style="color: #0000ff">)</span><span>"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"Installation Failed:`t`t`t</span><span style="color: #0000ff">$($Global</span><span>:</span><span style="color: #00ccff">FailedCount</span><span style="color: #0000ff">)</span><span>"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"VMs Not Running:`t`t`t</span><span style="color: #0000ff">$($Global</span><span>:</span><span style="color: #00ccff">VMsNotRunningCount</span><span style="color: #0000ff">)</span><span>"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"Extension Not Compatible with OS:`t</span><span style="color: #0000ff">$($Global</span><span>:</span><span style="color: #00ccff">OSNotCompatibleCount</span><span style="color: #0000ff">)</span><span>`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"Total VMs Processed:`t`t`t</span><span style="color: #0000ff">$($</span></span><span style="color: #00ccff">TotalVMsProcessed</span><span style="color: #0000ff">)</span><span>"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"========================================================================`n`n"</span></code></div>
<div><span><p></p></span></div>
<div><code><span style="color: #00ccff">[string]</span><span style="color: #0000ff">$</span><span style="color: #00ccff">DateTimeNow</span><span> = get-date -Format </span><span style="color: #ff6600">"dd/MM/yyyy - HH:mm:ss"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"`n========================================================================`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"</span><span style="color: #0000ff">$($</span><span style="color: #00ccff">DateTimeNow</span><span style="color: #0000ff">)</span><span style="color: #ff6600"> - Install VM Extension Script Complete.`n"</span></code></div>
<div><code><span>Write-Host </span><span style="color: #ff6600">"========================================================================`n"</span></code></div>
<div><span><p></p></span></div>
<p></p>

<div>
<p id="executing-the-script">
&nbsp;
</p>
</div>

<h4><strong><span style="text-decoration: underline">Executing the Script:</span></strong></h4>
<a href="https://gallery.technet.microsoft.com/scriptcenter/Automate-Installation-of-4f3b21f5" target="_blank">Download the script from the TechNet Gallery</a>. Open a PowerShell prompt, change directory into the same location where you downloaded / saved the script to and type:

&nbsp;
<p style="padding-left: 30px"><code><strong>.\azure-install-vm-extension.ps1 </span><span style="color: #00ccff">-SubscriptionName </span><span>"Name of your subscription"</span></code></strong></p>

<div>

&nbsp;

If you wish to install the Extension (<em>script defaults to <strong>BGInfo</strong></em>) on All VMs in the Subscription, add the parameter: <code><strong><span style="color: #00ccff">-ProcessAllVMs</span></strong></code>

&nbsp;

<h4><strong><span style="text-decoration: underline">Script Output:</span></strong></h4>

</div>
Example output from the PowerShell script is shown in the screenshot below. The script displays a "Progress Bar" that calculates the Percentage Complete based on number of VMs in the subscription. It also shows the Name of the VM that is currently being processed and finally once complete it provides Individual Counters and a Total for the number of Extension Installations:
<p style="padding-left: 30px"><img width="1024" height="800" class="alignnone size-large wp-image-2387" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/azure-install-vm-extension-2-1024x800.png" /></p>
&nbsp;

<h4><span style="text-decoration: underline"><strong>What about installing VM Extensions other than BGInfo?</strong></span></h4>
As described earlier, the script can be used to install any VM Extension, as the "Extension Name" (Type) and "PublisherName" are both parameters. To obtain a full list of the VM Extensions that are available in a particular region you can use the PowerShell code below.

This example returns the "<strong>Type</strong>" (<em>Name</em>) and "<strong>PublisherName</strong>" for All VM Extensions in the UK South region:

&nbsp;
<div>
<div style="padding-left: 30px"><code><span style="color: #339966">#  Edit below location variable with your target region</span></code></div>
<div style="padding-left: 30px"><code><span><span style="color: #0000ff"><span style="color: #00ccff">[string]</span>$</span><span style="color: #00ccff">location</span> =<span style="color: #ff00ff"> "uksouth"</span></span></code></div>
<div style="padding-left: 30px"></div>
<div style="padding-left: 30px"><code><span><span style="color: #ff6600">Get-AzureRmVmImagePublisher </span>-Location <span style="color: #0000ff">$</span><span style="color: #00ccff">location</span> | `</span></code></div>
<div style="padding-left: 30px"><code><span><span style="color: #ff6600">Get-AzureRmVMExtensionImageType</span> | `</span></code></div>
<div style="padding-left: 30px"><code><span><span style="color: #ff6600">Get-AzureRmVMExtensionImage</span> | Select Type, PublisherName | ft *</span></code></div>
&nbsp;
<div>Another common requirement and highly recommended practice is to install Anitmalware on all VMs, this could be <a href="https://docs.microsoft.com/en-us/azure/security/azure-security-antimalware">Microsoft's Antimalware Extension</a> or an alternative offered by one of our partners. To use the sample script above to install the Microsoft Antimalware Extension all you would need to do is set the two parameters below:</div>
&nbsp;
<div style="padding-left: 30px"><code><span style="color: #00ccff">-VMExtensionName</span> = "<span style="color: #ff00ff">IaaSAntimalware</span>"</code></div>
<p style="padding-left: 30px"><code><span style="color: #00ccff">-VMExtensionPublisher</span> = "<span style="color: #ff00ff">Microsoft.Azure.Security</span>"</code></p>

<h3></h3>
<strong>Additional Info:</strong> If you plan to install the Microsoft Antimalware Extension, you may also want to consider configuring "<em>File Path, Process Name or File Extension Exclusions for the Real Time Scanner</em>". As you know, file system exclusions vary depending on the specific workload deployed on a VM, however Microsoft provides a list of recommendations which are documented here: <a href="http://support.microsoft.com/kb/822158">Virus scanning recommendations for Enterprise computers that are running currently supported versions of Windows</a>

&nbsp;

To configure Exclusions for the "Real Time Scanner" component of the Microsoft Antimalware VM Extension, you need to either create a PowerShell hash table storing the properties or alternatively use a simple <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates">ARM JSON Template</a>, my preference is a JSON file and an example is shown below:

Save this file as "<strong>IaaSAntimalware-Config.json</strong>" after adding your required exclusions in the relevant sections.

&nbsp;
<div>
<div><code><span>{</span></code></div>
<div style="padding-left: 30px"><code><span>"AntimalwareEnabled"</span><span>: </span><span>true</span><span>,</span></code></div>
<div style="padding-left: 30px"><code><span>"RealtimeProtectionEnabled"</span><span>: </span><span>true</span><span>,</span></code></div>
<div style="padding-left: 30px"><code><span>"ScheduledScanSettings"</span><span>: {</span></code></div>
<div style="padding-left: 30px"><code><span>"isEnabled"</span><span>: </span><span>true</span><span>,</span></code></div>
<div style="padding-left: 60px"><code><span>"day"</span><span>: </span><span>"7"</span><span>,</span></code></div>
<div style="padding-left: 60px"><code><span>"time"</span><span>: </span><span>"120"</span><span>,</span></code></div>
<div style="padding-left: 60px"><code><span>"scanType"</span><span>: </span><span>"Quick"</span></code></div>
<div style="padding-left: 30px"><code><span> },</span></code></div>
<div style="padding-left: 30px"><code><span> </span><span>"Exclusions"</span><span>: {</span></code></div>
<div style="padding-left: 60px"><code><span> </span><span>"Extensions"</span><span>: </span><span>""</span><span>,</span></code></div>
<div style="padding-left: 60px"><code><span>"Paths"</span><span>: "</span><span>"</span><span>,</span></code></div>
<div style="padding-left: 60px"><code><span>"Processes"</span><span>: </span><span>""</span></code></div>
<div style="padding-left: 30px"><code><span> }</span></code></div>
<div><code><span>}</span></code></div>
</div>
</div>
&nbsp;

To define Exclusions in the "<code>Extensions</code>", "<code>Paths</code>" and "<code>Processes</code>" sections of the JSON file, the syntax is a "<strong>List of Semi-Colon Delimited Values</strong>". "<code>Paths</code>" also require an escape character for the backslash (<em>i.e - double backslashes</em>). For example:

&nbsp;
<p style="padding-left: 30px"><code>"Paths": "%windir%\\SoftwareDistribution\\Datastore\\DataStore.edb<strong>;</strong>%windir%\\SoftwareDistribution\\Datastore\\Logs\\Edb.chk",</code></p>
&nbsp;

For full details on how to configure Antimalware Settings such as Exclusions, see <a href="https://docs.microsoft.com/en-us/azure/security/azure-security-antimalware">Microsoft Antimalware for Azure Cloud Services and Virtual Machines</a>.

&nbsp;

Now that you have created your "Default File Exclusions Configuration File" saved in JSON format, we can use the <code><span style="color: #00ccff"><strong>-VMExtensionSettingsFilePath</strong></span></code> parameter to pass the File Path of the JSON file to the script. An example PowerShell command line to install the Microsoft Antimalware Extension is shown below:
<p>&nbsp;

<p style="padding-left: 30px"><code>.\azure-install-vm-extension.ps1 <span style="color: #00ccff">-SubscriptionName</span> <span style="color:#ff6600">"Visual Studio Enterprise"</span> <span style="color: #00ccff">-VMExtensionName</span> <span style="color:#ff6600">"IaaSAntimalware"</span> <span style="color: #00ccff">-VMExtensionPublisher</span> <span style="color:#ff6600">"Microsoft.Azure.Security"</span> <span style="color: #00ccff">-VMExtensionWindowsCompatible</span> <span style="color: #0000ff">$true</span> <span style="color: #00ccff">-VMExtensionLinuxCompatible</span> <span style="color: #0000ff">$false</span> <span style="color: #00ccff">-VMExtensionSettingsFilePath</span> <span style="color:#ff6600">"C:\scripts\IaaSAntimalware-Config.json"</span> <span style="color: #00ccff">-ProcessAllVMs</span></code>

<p>
&nbsp;

The example above could be used to "<strong>Automate the installation of the Microsoft Antimalware Extension on All Windows VMs running in a Subscription.</strong>"

</div>
&nbsp;

I am sharing this sample script as I believe it will be useful to other customers who have a requirement to automate the installation of VM Extensions across their Azure IaaS environments. However.......

&nbsp;
<p style="padding-left: 30px"><strong><span style="color: #ff0000">Important</span></strong>: As mentioned earlier regarding Warranty of sample scripts. Please ensure that you Test Automation Scripts against a "Test / Dev Subscription" or against a "Subset of VMs" prior to implementing against your Production Subscription(s). You are responsible for testing and validating the desired outcomes are achieved by the code you execute.</p>
&nbsp;


<strong><u>Possible Future Improvements:</u></strong>
<ol>
	<li>Make use of <a>PowerShell Jobs</a> to process multiple VM Extension installations concurrently.</li>
	<li>Add an option to turn on VM's that are not running, install the extension, then turn them off again.</li>
	<li>Update script to work with Azure Automation.</li>
	<li>Add code to allow a -Whatif parameter, to report on what the script "would do", useful for Change Control documentation.</li>
	<li>...The list is endless, but I hope to find time to add some of these soon.... :-)</li>
</ol>
&nbsp;

If you find this sample script useful, please add a comment or suggestion below.

&nbsp;
<div>
<h4><span style="text-decoration: underline"><b>This is useful for existing VMs, but what about new VM deployments?</b></span></h4>
Ok, yes yes, this is only half of the story! The sample script above is great for installing Extensions on existing VMs that have already been deployed into a subscription. However as we continue to transition into an "<a href="https://blogs.technet.microsoft.com/devops/2016/01/27/vorlonjs-a-journey-to-devops-infrastructure-as-code-with-microsoft-azure-and-resource-manager/">Infrastructure as Code</a>" (<em>JSON Templates</em>) and "<a href="https://blogs.msdn.microsoft.com/azuredev/2017/02/11/iac-on-azure-an-introduction-of-infrastructure-as-code-iac-with-azure-resource-manager-arm-template/">Configuration as Code</a>" (<a href="https://msdn.microsoft.com/en-us/powershell/dsc/overview"><em>DSC</em></a>) world, the recommended approach is to provision (<em>deploy</em>) your VMs with the required Extensions (<em>and Configuration</em>) automatically installed. This can be achieved by leveraging the declarative programming model offered by <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates">ARM JSON Templates</a>.

&nbsp;

In its simplest form, adding the code below to an existing JSON would automatically install the BGInfo Extension on a VM when it is deployed:

</div>
<p style="padding-left: 30px"><code>{</code></p>
<p style="padding-left: 60px"><code>"type": "Microsoft.Compute/virtualMachines/extensions",</code>
<code>"name": "BGInfo",</code>
<code>"apiVersion": "2016-04-30-preview",</code>
<code>"scale": null,</code>
<code>"location": "[resourceGroup().location]",</code>
<code>"properties": {</code></p>
<p style="padding-left: 90px"><code>"publisher": "Microsoft.Compute",</code>
<code>"type": "BGInfo",</code>
<code>"typeHandlerVersion": "2.1",</code>
<code>"autoUpgradeMinorVersion": true</code></p>
<p style="padding-left: 60px"><code>}</code></p>
<p style="padding-left: 30px"><code>}</code></p>

<div>

&nbsp;

The "<code><strong>Name</strong></code>" property can be customised if you want to include the VM Name, but this is optional. And you would also require a "<code><strong>dependsOn</strong></code>" property specifying <strong><code>"[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]"</code></strong> or similar, depending on the parameter name you use for the VMs Name.

&nbsp;

There are lots of other useful examples on the <a href="https://docs.microsoft.com/en-gb/azure/virtual-machines/windows/extensions-configuration-samples">Azure Windows VM Extension Configuration Samples</a> page that provide details of how to automatically <strong>install Extensions</strong> as part of your VM deployments. Using JSON based templates allows you to make further use of parameters.json files to store all of the settings (<em>such as Antimalware Exclusions for specific roles</em>) along with the code that deploys and configures the VM.

&nbsp;

</div>
And finally, there are hundreds of <a href="https://azure.microsoft.com/en-gb/resources/templates/">Azure Quickstart Templates</a> resources available in the gallery. These provide <strong>JSON Template</strong> examples for tasks such as how to "Join a VM to an existing AD Domain", through to deploying "Storage Spaces Direct (S2D)" or even deploying an entire "RDS Farm" automatically.

&nbsp;
<div>

I hope you found this blog post useful.

</div>
<div>

&nbsp;

Cheers

Neil

&nbsp;

</div>]]></content:encoded>
    </item>
  </channel>
</rss>