<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Brian Farnhill</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/brian_farnhill/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/feed.xml</link>
    <description>PowerShell and Azure Premier Field Engineer</description>
    <lastBuildDate>Thu, 26 Oct 2017 04:56:42 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Moving my blog!</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/moving-my-blog</link>
      <pubDate>Thu, 26 Oct 2017 14:56:42 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=665</guid>
      <description><![CDATA[I've made the decision to move my blog to a personal website - don't worry though, I'll continue...]]></description>
      <content:encoded><![CDATA[I've made the decision to move my blog to a personal website - don't worry though, I'll continue posting about similar things so the content won't change! Head on over to <a href="https://brianfarnhill.com/blog/">https://brianfarnhill.com/blog/</a> and check it out (don't forget to update your RSS readers!)]]></content:encoded>
    </item>
    <item>
      <title>Using the launch.json file in VS code for improved PowerShell debugging</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/using-the-launch-json-file-in-vs-code-for-improved-powershell-debugging</link>
      <pubDate>Wed, 27 Sep 2017 11:42:12 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=545</guid>
      <description><![CDATA[Anyone that has talked to me recently about how I write PowerShell scripts will know that Visual...]]></description>
      <content:encoded><![CDATA[Anyone that has talked to me recently about how I write PowerShell scripts will know that <a href="https://code.visualstudio.com/">Visual Studio Code</a> is my new one and only! You can write PowerShell scripts in there and hit F5 to have a similar debugging experience to what you would get in PowerShell ISE - but did you know that you can extend that to make it easier to debug and test different scenarios from within VS Code? If the answer to that is no, then read on!
<h2>What is launch.json?</h2>
VS Code has a number of files that it will look for when you open a directory that it will use to customise the way it behaves, launch.json is one of these. If you place this file at "/.vscode/launch.json" within a directory that you are working with, you'll be able to create customised ways to launch the debugger for your project. This applies to any language that VS Code supports debugging for, not just PowerShell - but let me show you what I'm talking about in some detail.
<h2>Working without launch.json</h2>
First lets talk about what happens when you debug a PowerShell script in VS Code without a launch.json file in place.

<a href="https://msdnshared.blob.core.windows.net/media/2017/09/ps1.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/09/ps1-300x209.png" alt="" width="300" height="209" class="alignnone size-medium wp-image-555" /></a>

In this screen shot you can see a few things. Firstly note that in the debug tab I see "No Configurations" - this is what happens when there is no launch.json file (or when you have one that has an empty configurations element in it). But you can see I can still press F5 (or use the little green arrow icon in the debug tab) to run my script and VS Code will launch the current PowerShell file and attach the debugger to it using the integrated PowerShell host.

The last thing you'll notice is that next to the "No Configurations" drop down that you see a cog shaped icon with a red dot over it - this is an indicator that you don't have a launch.json file or that there is some sort of issue with it. If you click on this you'll actually see that VS code will create a launch.json file for you with a couple of default configurations (which will be based on PowerShell because we have a PowerShell file open in our directory)
<h2>The default PowerShell launch.json file</h2>
When you add the default launch.json file to a folder with PowerShell files you'll see the following new "Configurations" that appear in the drop down that previously said "No configurations"

<a href="https://msdnshared.blob.core.windows.net/media/2017/09/ps2.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/09/ps2-300x120.png" alt="" width="300" height="120" class="alignnone size-medium wp-image-565" /></a>

Now these are all fairly common scenarios for when you want to debug a PowerShell script, here are some of the times you might use each of them:
<ul>
 	<li><strong>Launch current file</strong> - this is the same as what you had without a launch.json file, it will launch the currently open file and attach the debugger</li>
 	<li><strong>Launch current file in a temporary console</strong> - this is similar to the above, but instead of using the current integrated PowerShell host VS code will create a new host just to execute this file, and it will remove the host when the script completes. This is great if you need to validate your script in a perfectly clean PowerShell session on each test run, just remember to put a pause or breakpoint at the end of your script if you need to see the output, as the host will immediately close when the script finishes</li>
 	<li><strong>Launch current file /args prompt</strong> - This option will run the current file, the same as the first option does - but before it runs you will be prompted to enter any arguments that should be passed to the script when it runs. It looks like this:
<a href="https://msdnshared.blob.core.windows.net/media/2017/09/ps3.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/09/ps3-300x38.png" alt="" width="300" height="38" class="alignnone size-medium wp-image-575" /></a></li>
 	<li><strong>Attach to host process</strong> - This option will allow you to attach to an existing process. This option is useful if you are trying to debug PowerShell that is executing in an external process (such as debugging a DSC resource). You'll be prompted to select which process to attach to and you can debug from there</li>
 	<li><strong>Interactive session</strong> - This option will simply attach the debugger to the current integrated PowerShell host without actually running a script. You can then type commands in to the session window as you please and any breakpoints that are triggered in scripts will still be hit as usual, giving you the flexibility to call anything you want in any way that you can possibly call it from PowerShell</li>
</ul>
So you can see that with the default launch.json file you gain quite a lot of flexibility beyond the old 'F5' experience. But we can take things further than this.
<h2>Customising launch.json</h2>
Now lets assume that you want to debug your scripts in a way that doesn't line up with one of those default options, you can actually configure the launch.json file to have custom configurations that you create yourself. In this example, I'm going to work with a basic script file called "test.ps1" which looks like this:
<div>
<pre><span>param</span><span>(</span>
<span> </span><span>[Parameter</span><span>(</span><span>Mandatory</span><span>=$false)</span><span>]</span>
<span> </span><span>[ValidateSet</span><span>("Date", "Time")</span><span>]</span>
<span> </span><span>[String]</span>
<span> </span><span>$</span><span>OutputMode</span><span> </span><span>=</span><span> </span><span>"Time"</span>
<span>)</span>
<span>switch</span><span>($</span><span>OutputMode</span><span>)</span>
<span>{</span>
<span> </span><span>"Date"</span><span> {</span>
<span> </span><span>$</span><span>dateString</span><span> </span><span>=</span><span> </span><span>[DateTime]</span><span>::Now.ToShortDateString</span><span>()</span>
<span> }</span>
<span> </span><span>"Time"</span><span> {</span>
<span> </span><span>$</span><span>dateString</span><span> </span><span>=</span><span> </span><span>[DateTime]</span><span>::Now.ToShortTimeString</span><span>()</span>
<span> }</span>
<span>}</span>
<span>Write-Output</span><span> </span><span>"I was executed at </span><span>$</span><span>dateString</span><span>"</span></pre>
</div>
Basically all the script does is to output either the current date or the current time, based on a parameter that is optionally passed in to the file itself. Now if you want to add a new configuration you can open the launch.json file up and use the "Add configuration" button, which will then let you select from a range of "stubs" that you can choose for your configuration as a starting point.
<div><a href="https://msdnshared.blob.core.windows.net/media/2017/09/ps4.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/09/ps4-300x241.png" alt="" width="300" height="241" class="alignnone size-medium wp-image-585" /></a></div>
Here there are some PowerShell options in the list that look similar to the defaults. For my example I'm going to choose to add the "launch script" activity - here I can tell VS Code to run a specific script file (no matter what is open in the current window) and I can optionally add arguments to this call as well. I'll add two activities here so I can call test.ps1 with each of the argument options from the debugger there, one for "Date" and the other for "Time". The two new activities in launch.json look like this:
<div>
<pre><span> {</span>
<span> </span><span>"type"</span><span>: </span><span>"PowerShell"</span><span>,</span>
<span> </span><span>"request"</span><span>: </span><span>"launch"</span><span>,</span>
<span> </span><span>"name"</span><span>: </span><span>"Run test.ps1 with Date"</span><span>,</span>
<span> </span><span>"script"</span><span>: </span><span>"${workspaceRoot}/test.ps1"</span><span>,</span>
<span> </span><span>"args"</span><span>: [</span><span>"Date"</span><span>],</span>
<span> </span><span>"cwd"</span><span>: </span><span>"${workspaceRoot}"</span>
<span> },</span>
<span> {</span>
<span> </span><span>"type"</span><span>: </span><span>"PowerShell"</span><span>,</span>
<span> </span><span>"request"</span><span>: </span><span>"launch"</span><span>,</span>
<span> </span><span>"name"</span><span>: </span><span>"Run test.ps1 with Time"</span><span>,</span>
<span> </span><span>"script"</span><span>: </span><span>"${workspaceRoot}/test.ps1"</span><span>,</span>
<span> </span><span>"args"</span><span>: [</span><span>"Time"</span><span>],</span>
<span> </span><span>"cwd"</span><span>: </span><span>"${workspaceRoot}"</span>
<span> }</span></pre>
</div>
Now as soon as I have added these to launch.json I see that they appear in my configurations drop down...

<a href="https://msdnshared.blob.core.windows.net/media/2017/09/ps5.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/09/ps5-300x155.png" alt="" width="300" height="155" class="alignnone size-medium wp-image-595" /></a>

... and when I select each one, I see my test script will run with the debugger attached but with the appropriate arguments passed

<img src="https://msdnshared.blob.core.windows.net/media/2017/09/ps6-300x209.png" alt="" width="300" height="209" class="alignnone size-medium wp-image-605" />

This is a pretty simple example of how you can customise the launch.json file - I'm sure next time you find yourself debugging PowerShell scripts that you can find some ways to use this. Another example of how I'm using it is a recent change to the SharePointDsc project - we are adding the option to run the currently open unit test file but to simulate either SharePoint 2013 or 2016 based on which arguments are passed to the test file. Hopefully this is useful for you as well. Happy debugging!]]></content:encoded>
    </item>
    <item>
      <title>"The provided value for the template parameter is not valid" in ARM template deployments</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/the-provided-value-for-the-template-parameter-is-not-valid-in-arm-template-deployments</link>
      <pubDate>Thu, 14 Sep 2017 12:58:32 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=535</guid>
      <description><![CDATA[I've come across this one before, but I hit it again today and I wanted to share the details of what...]]></description>
      <content:encoded><![CDATA[I've come across this one before, but I hit it again today and I wanted to share the details of what happened. Basically the scenario is this, I have an ARM template that I want to deploy and it takes in parameters. The deployment needs to run through PowerShell using the Azure RM PowerShell cmdlets. When I call New-AzureRmResourceGroupDeployment I get the error that says "The provided value for the template parameter is {parameter name} not valid", and this is where things start getting weird. Here is an example of how I was calling it:
<pre>New-AzureRmResourceGroupDeployment -ResourceGroupName "MyRGName" `
                                   -Name "MyDeployment" `
                                   -TemplateUri "https://url.of/MyTemplate.json" `
                                   -TemplateParameterObject @{
                                       Parameter1 = "Value"
                                       Parameter2 = "Other value"
                                   }</pre>
Now what you're looking at here is perfectly valid PowerShell - it gets parsed by the engine, compiled and executed as you would expect it to, but for some reason I was getting the error about Parameter1 not being a valid value. This error typically comes up where there is a data type mismatch or some other type of parameter validation error - such as asking for a string in the template but you give it a number, or you didn't provide a mandatory value, or those sorts of things. But in my case, none of that was true - the data type was a string and there was a value and that's all there was to it. I even put the parameter values in to an external file and ran that, and that worked! So I knew there was no issue with the value I was providing, so I had to figure out why it wouldn't accept it in the format I was providing. I started trying things, I tried taking the hashtable out in to a separate variable and that didn't seem to help - but then I found another way of calling things that seemed to do the trick:
<pre>$params = @{}
$params.Add("Parameter1", "Value")
$params.Add("Parameter2", "Other value")

New-AzureRmResourceGroupDeployment -ResourceGroupName "MyRGName" `
                                   -Name "MyDeployment" `
                                   -TemplateUri "https://url.of/MyTemplate.json" `
                                   -TemplateParameterObject $params</pre>
&nbsp;

For those not familiar, the first line of that example is creating a new and empty hashtable (it's the equivalent of running 'New-Object -TypeName System.Collections.Hashtable'). From there I call the .Add() method to add each item to the hashtable rather than doing it inline like I was doing in the first example. Now I've done this both ways in separate PowerShell windows and I absolutely cannot for the life of me pick a difference between a hashtable declared in the first example and the second one. They are the same object time, they compare the same, they have the same keys and values ... and yet for some reason when I pass the second example in to the Azure RM cmdlets it works, while the first is clearly losing the value somewhere along the way.

So I don't have an explanation on the "why" in this case, but I can tell you that the second example works where the first one doesn't. I've observed this behavior from both my laptop when I run cmdlets, as well as in Azure Automation when I try to do a deployment from a runbook as well, so look out for it anywhere the RM cmdlets run. I hope this helps other people get around this one if you come up against it!]]></content:encoded>
    </item>
    <item>
      <title>Adding additional intellisense to VS Code when editing PowerShell scripts</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/adding-additional-intellisense-to-vs-code-when-editing-powershell-scripts</link>
      <pubDate>Mon, 21 Aug 2017 10:18:31 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=505</guid>
      <description><![CDATA[Most people that I talk to about my experiences writing PowerShell scripts these days will know that...]]></description>
      <content:encoded><![CDATA[Most people that I talk to about my experiences writing PowerShell scripts these days will know that Visual Studio Code is my absolute preference for editor these days. It's a great tool with lots of little tricks to help you improve your productivity - and recently I came across another one to help when I work on SharePoint related scripts for SharePointDsc, and this is how I add intellisense for SharePoint PowerShell cmdlets when I'm authoring scripts on my laptop (which doesn't have SharePoint installed on it).

If you start to rip apart how VS Code provides it's intellisense for PowerShell you'll see that it is able to do so through the use of a PowerShell Integrated Terminal - the program keeps this open in the background so that it can make calls to this session to get details to aid it's intellisense and other PowerShell interactivity.

<a href="https://msdnshared.blob.core.windows.net/media/2017/08/ps1.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/08/ps1-300x207.png" alt="" width="300" height="207" class="alignnone size-medium wp-image-506" /></a>

Now by default if I try typing something like "New-SPSite" in to this PowerShell file you'll see that the intellisense doesn't give me anything back to help.

<a href="https://msdnshared.blob.core.windows.net/media/2017/08/ps2.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/08/ps2-300x87.png" alt="" width="300" height="87" class="alignnone size-medium wp-image-516" /></a>

But if we know that VS Code draws its intellisense data from the PowerShell session, we can actually load the SharePoint cmdlets up and have them available. Now if I was running VS Code from a machine that has SharePoint installed it would be easy, I could just type the command to load the snap-in to the terminal and the cmdlets would then be available. However given I don't have SharePoint installed on my laptop that isn't an option. I do however know that to make sure the unit tests for SharePointDsc run that we keep non-functional copies of the SharePoint PowerShell cmdlets in what we call "stub" files, which will provide me enough of a structure to get this intellisense without needing SharePoint installed (if you are curious about how we came up with this approach, I have a <a href="https://blogs.msdn.microsoft.com/brian_farnhill/2015/09/27/better-approaches-to-unit-testing-powershell-scripts-that-call-sharepoint-cmdlets/">previous blog post that addresses this topic</a>). So if I want to load those up so I can add the intellisense I just need to use Import-Module to the path of either the 2013 or the 2016 cmdlets.
<pre>Import-Module C:\repos\SharePointDsc\Tests\Unit\Stubs\SharePoint\16.0.4456.1000\Microsoft.SharePoint.PowerShell.psm1</pre>
In that example I have the source code for SharePointDsc saved at C:\repos\SharePointDsc - so update the path accordingly to where you have the code stored. But once I have run that command you'll see that the intellisense lights up in full!

<a href="https://msdnshared.blob.core.windows.net/media/2017/08/ps3.png"><img src="https://msdnshared.blob.core.windows.net/media/2017/08/ps3-300x237.png" alt="" width="300" height="237" class="alignnone size-medium wp-image-525" /></a>

This makes it much easier to author scripts without needing to have SharePoint installed, improving my productivity when authoring SharePointDsc. You could use these for any SharePoint script, not just working with SharePointDsc - just make sure you have the code for SharePointDsc around to load the stubs up and then you can use this for any script. I hope that helps improve how you write your scripts guys!

&nbsp;

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>Getting IDs to use with the Package DSC resource</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/getting-ids-to-use-with-the-package-dsc-resource</link>
      <pubDate>Tue, 04 Jul 2017 11:34:07 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=485</guid>
      <description><![CDATA[One of the questions I get from customers who are looking at using the Package DSC resource to...]]></description>
      <content:encoded><![CDATA[One of the questions I get from customers who are looking at using the Package DSC resource to install MSI's and executables is "What is the ProductID for the installer I want to run?". If you look at the documentation the ProductID is meant to be a unique identified for the product you are installing, but how can you figure out what the correct value for any given installation is? Lucky for us you can actually use a little bit of PowerShell to look this up after you manually install the product once. Let me give you an example, I recently had to write a DSC script which would install SQL Server Management Studio 17.1 with DSC, so here is the approach I took.

<strong>Step one: Manually install the product somewhere</strong>

This is pretty straightforward - but we need to manually install it once so that we can look up the product ID from the registry after the fact. We can uninstall it after we are done if you like - I tend to provision a quick virtual machine in Azure for this to get the ID and then I can delete the VM after I'm done with it.

<strong>Step two: Find the ID with PowerShell</strong>

Since all the IDs of products that are installed on a machine are stored in the registry, it's actually quite straightforward to interrogate the registry to get a list of products with their corresponding ID is. Here is the script I use to output the list of installed products:
<pre>$x86Path = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$installedItemsX86 = Get-ItemProperty -Path $x86Path | Select-Object -Property DisplayName, PSChildName
    
$x64Path = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*"
$installedItemsX64 = Get-ItemProperty -Path $x64Path | Select-Object -Property DisplayName, PSChildName

$installedItems = $installedItemsX86 + $installedItemsX64
$installedItems | Where-Object -FilterScript { $null -ne $_.DisplayName } | Sort-Object -Property DisplayName | Out-GridView</pre>
This will output you a GridView in PowerShell that looks a little like this

<a href="https://msdnshared.blob.core.windows.net/media/2017/07/4-07-2017-11-19-36-AM.png"><img width="300" height="207" class="alignnone size-medium wp-image-495" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/4-07-2017-11-19-36-AM-300x207.png" /></a>

And once we have it in a GridView we can actually add filters pretty easily as well

<a href="https://msdnshared.blob.core.windows.net/media/2017/07/4-07-2017-11-20-30-AM.png"><img width="300" height="87" class="alignnone size-medium wp-image-496" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/07/4-07-2017-11-20-30-AM-300x87.png" /></a>

Now we can see what the GUID is for SQL Management Studio 17.1 and I can now use this to make a DSC resource

<strong>Step three: Create the DSC resource</strong>

Now that I have the ID I can craft my resource
<div>
<pre><span>Package InstallSSMS</span>
<span>{</span>
<span>    Ensure </span><span>=</span><span> </span><span>"Present"</span>
<span>    Name </span><span>=</span><span> </span><span>"SSMS-Setup-ENU"</span>
<span>    Path </span><span>=</span><span> </span><span>"C:\Software\ssms2016\SSMS-Setup-ENU.exe"</span>
<span>    Arguments </span><span>=</span><span> </span><span>"/install /passive /norestart"</span>
<span>    ProductId </span><span>=</span><span> </span><span>"b636c6f4-2183-4b76-b5a0-c8d6422df9f4"</span>
<span>}</span></pre>
<div>Here you can see I've used the ID we retrieved in step 2 as the ProductId attribute, and that I'm also passing arguments to make sure the installer won't need any user input (remember, DSC runs in the background so just running the installer on it's own without those arguments won't help).</div>
</div>
&nbsp;

But that's all there is to it - a nice simple script to help you figure out the ProductID values to use in your package resource. Happy DSC'ing!

&nbsp;

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>Running Azure Automation runbooks from IFTTT tasks</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/running-azure-automation-runbooks-from-ifttt-tasks</link>
      <pubDate>Wed, 09 Nov 2016 09:36:43 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=405</guid>
      <description><![CDATA[I recently had an idea (ok lets call it 'collaboratively came up with while talking to one of the...]]></description>
      <content:encoded><![CDATA[I recently had an idea (ok lets call it 'collaboratively came up with while talking to one of the grads in our office) to look at ways that I could trigger runbooks in my Azure Automation account from my phone. The specific issue we were looking to solve in this case was "how can I easily shut down all the VMs in my subscription from my phone if I forget to turn them off". Now I already have a runbook that was scheduled to shut down the VMs every night at 11pm as a 'just in case' but surely there was a better way if I remembered before than to rely on that automation schedule. After a little bit of thought, the idea of create a "<a href="https://ifttt.com/products/do/button">do button</a>" from <a href="https://ifttt.com">IFTTT</a> (stands for "if this then that", great little automation site that I recommend you check out if you haven't used it already) seemed to be the perfect solution for me. So here's a run through of the solution from start to end.
<h2>Creating the runbook</h2>
For my example here my runbook I wanted to look for all Virtual Machines in my subscription and shut them down to avoid excess billing. The script I use for this is fairly straightforward:
<pre>$connectionName = "AzureRunAsConnection"
try
{
    $servicePrincipalConnection = Get-AutomationConnection -Name $connectionName        

    Add-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection.TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint | Out-Null
}
catch 
{
    if (!$servicePrincipalConnection)
    {
        $ErrorMessage = "Connection $connectionName not found."
        throw $ErrorMessage
    } 
    else
    {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}

Write-Output -InputObject "Looking for VMs that are running"
Get-AzureRmVm | ForEach-Object -Process {
    $status = Get-AzureRmVM -ResourceGroupName $_.ResourceGroupName -Name $_.Name -Status
    if($status.Statuses | Where-Object -FilterScript { $_.Code -match "PowerState/running" })  
    {
        Write-Output "Stopping VM [$($_.Name)] from Resource Group [$($_.ResourceGroupName)]"
        $_ | Stop-AzureRmVM -Force
    }
}</pre>
This bit of script basically calls out all the VMs in the subscription, checks their power state and if they are running, we pipe out to Stop-AzureRMVM to tell Azure to de-provision the virtual machines.
<h2>Creating a webhook</h2>
The next piece of the puzzle is to create a <a href="https://en.wikipedia.org/wiki/Webhook">webhook </a>on the runbook. This allows the runbook to be triggered by a specific URL being called. To create a webhook for our runbook we simply go in to the runbook in the Azure portal, select "webhooks" and then select the "add webhook" at the top. You'll be presented with a screen like this:

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-10-11-AM.png"><img width="240" height="300" class="alignnone size-medium wp-image-415" alt="New webhook screen" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-10-11-AM-240x300.png" /></a>

Here you set whether or not a webbook can be enabled, when it expires (it's a good idea from a security stand point to not make this a never ending period of time, rather roll these over and use new URLs at regular intervals to keep them secret and secure) and you see the URL that will be created at the bottom there. Also note the security message at the top of the window there - the URL is never again shown anywhere in the portal and can't be retrieved again from here (again, for security purposes) so it's important to copy/paste the URL somewhere safe at this point so you don't lose it before you click the OK button to save the new webhook.
<h2>Build a new applet in IFTTT</h2>
Now things are set up on the Azure side of things, we need to look at how we can create an applet in IFTTT. To start sign in and go to the "my applets" screen and select "new applet".

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-16-43-AM.png"><img width="300" height="76" class="alignnone size-medium wp-image-425" alt="New applet" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-16-43-AM-300x76.png" /></a>

Next we click the "this" link and then need to search for the "do button" trigger, this allows us to tell IFTTT that our applet will be triggered whenever we press the specific "do" button in the mobile app on either an iOS or Android device that I've signed in to. It's worth noting that there are dozens and dozens of other ways to trigger things in IFTTT, the do button is the one that suits the specific type of function I need this time around, but you should also consider exploring some of their other recipes for applets and get a feel for other ways it can be triggered as well.

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-18-58-AM.png"><img width="300" height="207" class="alignnone size-medium wp-image-426" alt="do button" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-18-58-AM-300x207.png" /></a>

Select "button press" as the trigger and then we can select the "that" link and search for a task called "maker". Maker is designed to allow applets to be triggered by web requests, or to send our web requests in response to a trigger (which is exactly what we want this time around).

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-20-44-AM.png"><img width="300" height="227" class="alignnone size-medium wp-image-435" alt="Maker" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-20-44-AM-300x227.png" /></a>

Once we are at the maker configuration screen select "make a web request" and then we can craft the type of request we want to send. We start by pasting the URL in we created earlier for our webhook and then setting the method to "POST". Now in my example runbook for this scenario there is no need to craft a body as I don't have any parameters to pass to it, however if you adapt this scenario and need to pass parameters to a runbook, go and have a read of the <a href="https://azure.microsoft.com/en-us/documentation/articles/automation-webhooks/">Azure Automation Webhooks documentation</a> to get a feel for how that should be structured and how to use the variables in your runbooks.

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-23-36-AM.png"><img width="175" height="300" class="alignnone size-medium wp-image-445" alt="Maker example" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-23-36-AM-175x300.png" /></a>

Click create action and after this you'll be all set!
<h2>Triggering the applet</h2>
I have an android device so these steps will vary a little for someone on iOS, but basically once you install the <a href="https://itunes.apple.com/us/app/do-button-by-ifttt/id905998610?mt=8">"do button" app on iOS</a>, or the <a href="https://play.google.com/store/apps/details?id=com.ifttt.ifttt">IFTTT app on android</a> you can then trigger the do button. In my case I add a widget to the homescreen of my android that I can now simply press the button on, and this will trigger my action!

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/Screenshot_20161109-102843.png"><img width="169" height="300" class="alignnone size-medium wp-image-475" alt="Android home screen" src="https://msdnshared.blob.core.windows.net/media/2016/11/Screenshot_20161109-102843-169x300.png" /></a>

You can tell if it's being triggered in a couple of places. Firstly if you head to the IFTTT activity screen you should see where the action was triggered and if you allow GPS access to the IFTTT app it'll show you where it was triggered from, in this case you can see I ran it from the Microsoft office in Canberra.

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-31-43-AM.png"><img width="300" height="185" class="alignnone size-medium wp-image-455" alt="IFTTT activity" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-31-43-AM-300x185.png" /></a>

You can also go back to the runbook in the Azure portal and see that the run was completed by going in to the "jobs" section of the runbook.

<a href="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-32-17-AM.png"><img width="300" height="89" class="alignnone size-medium wp-image-465" alt="Runbook jobs" src="https://msdnshared.blob.core.windows.net/media/2016/11/9-11-2016-10-32-17-AM-300x89.png" /></a>

And that's it! Now when I remember that I haven't shut any of my VMs down it's a simple matter of unlocking my phone and pressing a single button - super straightforward to do and it leverages my existing runbook to do it! Now you can take this basic example and look at all the other ways you might want to trigger runbooks in Azure Automation using webhooks and extend it to better support your use of the cloud - Enjoy!]]></content:encoded>
    </item>
    <item>
      <title>xSharePoint is now SharePointDsc - what you need to know!</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/xsharepoint-is-now-sharepointdsc-what-you-need-to-know</link>
      <pubDate>Wed, 08 Jun 2016 08:58:02 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=385</guid>
      <description><![CDATA[For just over 12 months now we have been working hard to grow the xSharePoint DSC module to let...]]></description>
      <content:encoded><![CDATA[For just over 12 months now we have been working hard to grow the xSharePoint DSC module to let SharePoint 2013 and 2016 administrators use PowerShell Desired State Configuration to manage their SharePoint deployments. We've come a long way in the last year, and now with the help of my core team we are making an important transition - we are renaming from "xSharePoint" to "SharePointDsc".

This is important for a number of reasons for this - the most important of which is removing the "x" element. If you have ever had a read of the home page on GitHub for xSharePoint (and other "x" resources as well) we describe this as being a flag that the resource is "experimental". This was something that made sense when we first created the resources were first built - we needed to test and refine them, collect feedback from sources both internal and external to Microsoft, and add more functionality to make the module something much more than just an experiment. Now the time has come to move forward with a new name that indicates the "Production ready" state of our resources, and that is the main driver behind the change. There is an open <a href="https://github.com/PowerShell/PowerShell-RFC/blob/master/1-Draft/RFC0004-PowerShell-Module-Versioning.md" target="_blank">RFC from the PowerShell team</a> that touches on some of these issues in terms of how we approach versioning going forward with the PowerShell modules that is also worth a read for more information on what is happening in this space.
<h2>What specifically is changing in SharePointDsc</h2>
The items in the module that are changing moving forward are:
<ul>
	<li>The name will change from xSharePoint to SharePointDSC. This will mean that xSharePoint will no longer have new functionality published to the PowerShell gallery, version 0.12 will be the last version available under the old name [see note below on hotfixes]</li>
	<li>All SharePoint DSC resource names will be renamed to begin with just "SP" instead of "xSP". For example, xSPCreateFarm will become SPCreateFarm. This means updating your configurations to rename the resource types to the new name format. The parameters for each resource remain the same (with the exception of changes to schemas that were made after the 0.12 release - see the release notes for a <a href="https://github.com/PowerShell/SharePointDsc/blob/dev/CHANGELOG.md">list of changes in 1.0</a> to understand these changes)</li>
	<li>Other internal name changes will take place to ensure that all internal function names go from [verb]-xSharePoint[noun] to [verb]-SPDSC[noun]. These are not designed to be called directly though, but in the event that you have written scripts that do use these, you will need to use the new function names</li>
</ul>
<h3>Hotfixes for xSharePoint</h3>
As version 0.12 of xSharePoint is the last version that will be published with that name, we wanted to ensure that we had an approach that would mean we could provide support and hotfixes to customers who are using xSharePoint currently who are unable (for whatever reason) to move to SharePointDsc. For that reason, <a href="https://github.com/PowerShell/SharePointDsc/tree/xSharePoint">a separate branch</a> will be maintained with the code from the 0.12 release that we will be able to push specific hotfixes to in the event that a customer requires it. These will be published at increments to 0.12 (so from 0.12.1.0 to 0.12.2.0, 0.12.3.0 and so on). No new functionality will be added to this branch, SharePointDSC will be the module that receives all the new development effort - so customers should begin planning now for how to transition to the new module.
<h2>The release strategy for SharePointDsc</h2>
The next steps forward on how SharePointDsc is released are pretty simple. The next release (which is already <a href="https://www.powershellgallery.com/packages/SharePointDSC/1.0.0.0">available on the PowerShell gallery</a>) will be numbered 1.0. Moving forward we will be doing releases every month and will be following the final version number strategy that is decided upon when the <a href="https://github.com/PowerShell/PowerShell-RFC/blob/master/1-Draft/RFC0004-PowerShell-Module-Versioning.md" target="_blank">RFC from the PowerShell team</a> is closed and formalised. Releases will usually be done at the start of the month, with the next scheduled release to be 1.1 in early July.
<h2>How to prepare for the module rename to SharePointDsc</h2>
There are a number of steps to be taken when the updated module becomes available. The below list is a high level walkthrough of the primary considerations.
<h3>Distribution of the module</h3>
Firstly you need to consider how you will deploy the module to your servers. The good news here is that because the names of all of the DSC resources and cmdlets have been changed, there is no reason that you can't have xSharePoint and SharePointDsc on the same server as you transition. So the usual options for deploying these modules are available - such as deploying through PowerShellGet, adding them to a pull server (or Azure Automation if you are using it as your pull server), or through manually deploying the module to nodes you are pushing configurations to.
<h3>Updating configurations</h3>
Once you have the module available you can begin to update your configurations to use the new resource names. This will firstly mean changing the "Import-DscResource" cmdlet at the top of your config to use the "SharePointDsc" module and not "xSharePoint". After this begin to change the names of each resource from the module to remove the x off the front (so as described above, "xSPCreateFarm" becomes "SPCreateFarm").
<h3>Republish your new configurations</h3>
Once you have updated the configurations you can republish them to your existing SharePoint servers. The new resources behave exactly the same as the old resources, so this should not trigger any changes as the same test and set methods will be used - its just the names that have changed. So a configuration element that was in a compliant state before the change will continue to be compliant with the new name.
<h2>The future of SharePointDsc</h2>
We aren't finished with SharePointDsc yet, not by a long shot. We keep our <a href="https://github.com/PowerShell/xSharePoint/issues" target="_blank">backlog of features, fixes and enhancements</a> on GitHub - here you can see what we are working on, expected timelines for releases and you can even raise new issues for us to make any changes to the module here as well. We will be continuing to release updates with changes once a month and we will be constantly listening to feedback from the wider community. So raise an issue on GitHub, or post a comment here, and go out and do great things with SharePointDsc!]]></content:encoded>
    </item>
    <item>
      <title>Consuming preview builds of xSharePoint through PowerShellGet</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/consuming-preview-builds-of-xsharepoint-through-powershellget</link>
      <pubDate>Mon, 18 Apr 2016 13:59:39 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=355</guid>
      <description><![CDATA[PowerShellGet is a new module that is installed as part of PowerShell 5 (in Windows Management...]]></description>
      <content:encoded><![CDATA[PowerShellGet is a new module that is installed as part of PowerShell 5 (in <a href="https://www.microsoft.com/en-us/download/details.aspx?id=50395" target="_blank">Windows Management Framework 5.0</a>) which allows you to easily connect to remote repositories to install PowerShell modules from. When you install WMF5 you'll get a repository configured to point to the <a href="https://powershellgallery.com/" target="_blank">PowerShell Gallery</a>, where you can download the release versions of all of the PowerShell teams  DSC resources. However if you want to use the preview (in development but not yet released) builds, you have the option to connect to a secondary repository where the dev builds are created and install those directly to your machine.
<h2>Where are the dev builds for xSharePoint?</h2>
All of the DSC resources that are published by the PowerShell team on GitHub make use of AppVeyor to do the automated testing and build of the modules. For example, you can see the latest artifacts that have been built at <a href="https://ci.appveyor.com/project/PowerShell/xsharepoint/build/artifacts" target="_blank">https://ci.appveyor.com/project/PowerShell/xsharepoint/build/artifacts</a>. AppVeyor can also publish these as a Nuget feed, which has been configured for xSharePoint, with the feed URL being <a href="https://ci.appveyor.com/nuget/xsharepoint" target="_blank">https://ci.appveyor.com/nuget/xsharepoint</a>.
<h2>Adding a new repository to your local machine</h2>
To consume the preview builds we need to first connect the local machine with WMF5 installed on it to our remote repository. That can easily be done through using the <a href="https://technet.microsoft.com/en-us/library/dn807168.aspx" target="_blank">Register-PSRepository</a> cmdlet. Once the repository has been added you can easily request a list of the versions that are available there using the <a href="https://technet.microsoft.com/en-us/library/dn807167.aspx" target="_blank">Find-Module</a> cmdlet. Here you can see I've requested all the available versions, but only want it to show the latest 10 versions it has found (keeping in mind that the preview branch here has many versions in it, so looking at the full list may be 100 or so, potentially more, versions).

<a href="https://msdnshared.blob.core.windows.net/media/2016/04/18-04-2016-1-34-14-PM.png"><img class="aligncenter wp-image-365" src="https://msdnshared.blob.core.windows.net/media/2016/04/18-04-2016-1-34-14-PM-300x113.png" alt="" width="600" height="227" /></a>

The specific command that is being used here to register the repository is:
<pre>Register-PSRepository -Name xSharePointDev -SourceLocation https://ci.appveyor.com/nuget/xsharepoint</pre>
The repository name can be whatever you like, you use this to uniquely identify the source. The URL though is the Nuget feed URL we mentioned earlier.
<h2>Installing from the preview build feed</h2>
Installing a specific version from the preview feed is very straightforward. After using the Find-Module cmdlet you can pipe a specific version to <a href="https://technet.microsoft.com/en-us/library/dn807162.aspx" target="_blank">Install-Module</a> to tell PowerShell to download and install that version. Below you can see the installation, and then I verify the version that is installed with Get-Module.

<a href="https://msdnshared.blob.core.windows.net/media/2016/04/18-04-2016-1-33-48-PM.png"><img class="aligncenter wp-image-366" src="https://msdnshared.blob.core.windows.net/media/2016/04/18-04-2016-1-33-48-PM-300x77.png" alt="" width="599" height="154" /></a>
<h2>Updating to a newer version</h2>
After you have installed a specific version, you can install a new version in exactly the same way as when you installed the previous version. If you don't specify a version it will automatically get the latest build available. Here you can see the upgrade taking place, noting that if you do not use the "-Force" switch on the install cmdlet you will be presented an error. Once you do this you will now have two versions installed, the latest version as well as the previously installed version.

<a href="https://msdnshared.blob.core.windows.net/media/2016/04/18-04-2016-1-33-31-PM.png"><img class="aligncenter wp-image-376" src="https://msdnshared.blob.core.windows.net/media/2016/04/18-04-2016-1-33-31-PM-300x104.png" alt="" width="600" height="207" /></a>

That's all there is to it! Now the same processes can be used if you want to stick to the released versions instead of the developer builds, you just don't need to connect to the custom repository as the PowerShell gallery has these builds and that is automatically configured. But if you want the latest and greatest which may come with a few bugs from time to time, then you can use this to get these builds just as easily.

As always, for more information about xSharePoint or to raise issues or changes with it head over to the <a href="https://github.com/PowerShell/xSharePoint/" target="_blank">xSharePoint repository on GitHub</a>.]]></content:encoded>
    </item>
    <item>
      <title>Configuring SQL Server AlwaysOn Availability Groups in Azure RM Virtual Machines</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/configuring-sql-server-alwayson-availability-groups-in-azure-rm-virtual-machines</link>
      <pubDate>Thu, 03 Mar 2016 02:48:52 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/?p=261</guid>
      <description><![CDATA[This week on a project I came up against the need to set up some AlwaysOn Availability Groups (AG's)...]]></description>
      <content:encoded><![CDATA[This week on a project I came up against the need to set up some AlwaysOn Availability Groups (AG's) for SQL Server 2014 in an Azure IaaS environment, using v2 (or Resource Manager/ARM) based VMs as opposed to the v1 (or Service Manager/ASM) based ones. I came across varying bits of documentation to do with this, but it either didn't have the ARM PowerShell cmdlets or it was a mix and match of scripts and UI actions which didn't help me scripting the deployment, so after working through this, there are a few things that I learned.
<h3>The challenges</h3>
For the most part, the process of actually setting up the availability groups within the VMs was more or less the same as doing it on-premises so I wont go in to that. The issue I came across was that after I have provisioned my AGs and created listeners for them I found that I couldn't open SQL connections to the servers through the listener, but I could if I opened connections directly to each server. Name resolution was working fine and I could see that the DNS was in place to resolve to the listeners IP address, but the host was unreachable. After a bit of digging it turns out that to use AGs in Azure IaaS you need to put an internal load balancer in front of them, which will create an IP address on the virtual network which will allow for the Azure virtual network to know which VM to route traffic to correctly (more or less, I'm a little sketchy on the inner workings of the networking in Azure - but this is <a href="https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-sql-server-configure-ilb-alwayson-availability-group-listener/">documented as the solution to AAGs in Azure</a>).
<h3>Creating the internal load balancer</h3>
For the purposes of keeping this post brief and focussed on the ILB setup, lets assume that you already have your two (or more) SQL servers provisioned and you have configured the availability groups the same as you would on-prem. Below is the script that I used to create the load balancer.
<pre class="scroll"><code class="csharp">$ResourceGroupName = "My-ResourceGroup"
 $NetworkName = "VirtualNetwork01"
 $SubnetName = "Subnet1"
 $LoadBalancerName = "My-New-Loadbalancer"
 $FrontEndIpName = "My-New-Loadbalancer-FrontEnd"
 $FrontEndLocalIpAddress = "192.168.0.70"
 $BackEndPoolName = "My-New-LoadBalancer-Backend"
 
 $VirtualMachines = @("SQL-01", "SQL-02")
 
 
 $network = Get-AzureRmVirtualNetwork -Name $NetworkName -ResourceGroupName $ResourceGroupName
 $subnet = $network.Subnets | Where-Object { $_.Name -eq $SubnetName } | Select -First 1
 
 # Create the front end IP address for the load balancer
 $frontendIP = New-AzureRmLoadBalancerFrontendIpConfig -Name $FrontEndIpName -PrivateIpAddress $FrontEndLocalIpAddress -SubnetId $subnet.Id
 
 # Create the backend pool
 $beAddressPool = New-AzureRmLoadBalancerBackendAddressPoolConfig -Name $BackEndPoolName
 
 # Health probe
 $healthProbe = New-AzureRmLoadBalancerProbeConfig -Name "HealthProbe" -Protocol Tcp -Port 59999 -IntervalInSeconds 15 -ProbeCount 2
 
 # Load balancing rule config
 $lbrule = New-AzureRmLoadBalancerRuleConfig -Name "SQL" -FrontendIpConfiguration $frontendIP -BackendAddressPool $beAddressPool -Probe $healthProbe -Protocol Tcp -FrontendPort 1433 -BackendPort 1433 -EnableFloatingIP
 
 # Create the load balancer
 $NRPLB = New-AzureRmLoadBalancer -ResourceGroupName $ResourceGroupName -Name $LoadBalancerName -Location $network.Location -FrontendIpConfiguration $frontendIP -BackendAddressPool $beAddressPool -LoadBalancingRule $lbrule -Probe $healthProbe
 
 
 #Reload to get the IDs of what was created
 $beAddressPool = Get-AzureRmLoadBalancerBackendAddressPoolConfig -name $BackEndPoolName -LoadBalancer $NRPLB
 
 # For each VM add the back end pool to the IP configurations
 foreach($vmName in $VirtualMachines) {
 $vm = Get-AzureRmVm -ResourceGroupName $ResourceGroupName -Name $vmName
 $nicName = ($vm.NetworkInterfaceIDs[0].Split('/') | Select -Last 1)
 $nic = Get-AzureRmNetworkInterface -Name $nicName -ResourceGroupName $ResourceGroupName
 $nic.IpConfigurations[0].LoadBalancerBackendAddressPools = $beAddressPool
 Set-AzureRmNetworkInterface -NetworkInterface $nic
 
 Restart-AzureRmVM -Name $vmName -ResourceGroupName $ResourceGroupName
 }</code></pre>
&nbsp;

The basic flow of this script is to do the following:
<ul>
	<li>Create a front end IP address for the load balancer. This should be the same IP address that you use for your listener (this makes the assumption that you are working on a single subnet, I'll get to multi-subnet in a bit)</li>
	<li>Creates a "pool" to put the servers in that the load balancer will refer to. This should be every server in the availability group that shares the same subnet as the above IP address</li>
	<li>Creates a probe to check what VMs are currently responding, so that the network can direct traffic appropriately</li>
	<li>Defines a load balancer rule which specifies the ports to handle traffic on</li>
	<li>Takes all of the above objects and then creates the load balancer (nothing actually gets created in your tenant up to this point)</li>
	<li>For each of the specified VMs, it gets their first network adaptor and puts it in the backend pool that was created earlier</li>
	<li>Restart each VM so that the new config can apply</li>
</ul>
There is one thing that I want to highlight from the above script - the "EnableFloatingIP" switch on the New-AzureRmLoadBalancerRuleConfig cmdlet. Without this turned on for your rule you'll find that all traffic hitting your availability group will just time out. By enabling floating IPs and letting Azure move the IP address around based on it detecting that servers are down you'll see that traffic begins to actually flow correctly.

Next we need to make a couple of changes to the Windows Failover Cluster so that it plays nice with the load balancer that we just created. Here is the script that you should run on the active node in your cluster:
<pre class="scroll"><code class="csharp"> Import-Module FailoverClusters
 Get-ClusterResource "IPAddressResource" | Set-ClusterParameter -Name overrideaddressmatch -Value 1
 Get-ClusterResource "IPAddressResource" | Set-ClusterParameter -Name probeport -Value 59999
 Get-ClusterResource "IPAddressResource" | Set-ClusterParameter -Name SubnetMask -Value 255.255.255.255</code></pre>
<pre class="scroll"><code class="csharp"> </code></pre>
Just swap in the name of the IP address resource in your cluster here and you're good to go (look it up using Get-ClusterResource with no parameters to see a full list of the resources in your cluster). The thing that threw me a little here was the subnet mask - the subnet for my lab in this case was 255.255.255.224, so I originally tested with that in there, but I wasn't able to fail over as all the traffic was still being routed to the old primary. This is because the ILB created earlier was not able to poll correctly. Once this subnet mask was switched over I was able to see successful failover take place with the ILB routing traffic to the new primary within 15 seconds of me triggering the manual failover in SQL. So once you've got this sorted out you are ready to go. Failing over the AG works just the same as it always did, and your traffic should flow to the new primary from there.
<h3>Multi-subnet considerations</h3>
Now everything I have done so far is all well and good for running your AG on the one subnet, but what happens when there are nodes in your cluster that are in a separate subnet? The whole plan goes a little bit south because the internal load balancers in Azure are basically tied to a single subnet by the front end IP and in a failover cluster for SQL that uses multiple subnets your cluster will usually have at least two IP address resources, one for each subnet, and the appropriate address resource gets brought online as the AG fail over the cluster to a node on the other subnet. This means that for this scenario, you need to create an extra internal load balancer for each subnet (in the lab I had set up I was using 4 SQL servers, 2 in subnet A and 2 in subnet B). Each ILB I created had a front end address that was for the subnet I created it in, and referred to only the VMs in that subnet. With the additional ILBs in place, I was able to start failing SQL over to the second subnet and see things come together the way I needed.

&nbsp;

<hr style="width: 1px;color: #ffffff;border: 1px solid #CCCCCC" noshade="noshade" width="1" />

So there you have it - hopefully that will help you get going with AGs in Azure with Resource Manager cmdlets!

<script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script><script>// &lt;![CDATA[
if (typeof(lpcurruser) == &#039;undefined&#039;) lpcurruser = &#039;&#039;; if (document.getElementById(&#039;lpcurruserelt&#039;) &amp;&amp; document.getElementById(&#039;lpcurruserelt&#039;).value != &#039;&#039;) { lpcurruser = document.getElementById(&#039;lpcurruserelt&#039;).value; document.getElementById(&#039;lpcurruserelt&#039;).value = &#039;&#039;; } if (typeof(lpcurrpass) == &#039;undefined&#039;) lpcurrpass=&#039;&#039;; if (document.getElementById(&#039;lpcurrpasselt&#039;) &amp;&amp; document.getElementById(&#039;lpcurrpasselt&#039;).value != &#039;&#039;) { lpcurrpass = document.getElementById(&#039;lpcurrpasselt&#039;).value; document.getElementById(&#039;lpcurrpasselt&#039;).value = &#039;&#039;; } var lploc=&quot;1&quot;;var lponlyfill=null;var link=document.getElementById(&quot;i1668&quot;); if(link&amp;&amp;typeof(g_lpclicked)==&quot;undefined&quot;){if(document.createEventObject){var evt = document.createEventObject();link.fireEvent(&quot;onclick&quot;,evt);}else{var evt2 = document.createEvent(&quot;MouseEvents&quot;);evt2.initMouseEvent(&quot;click&quot;, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);link.dispatchEvent(evt2);}g_lpclicked=1;} if(lploc==3){var pholders=[&quot;idDiv_PWD_UsernameExample&quot;,&quot;idDiv_PWD_PasswordExample&quot;,&quot;i0116_hint&quot;,&quot;i0118_hint&quot;]; for(var i=0;i</script>]]></content:encoded>
    </item>
    <item>
      <title>Retrieving the URL of an app catalog site in SharePoint 2013</title>
      <link>https://docs.microsoft.com/archive/blogs/brian_farnhill/retrieving-the-url-of-an-app-catalog-site-in-sharepoint-2013</link>
      <pubDate>Mon, 21 Dec 2015 21:25:00 GMT</pubDate>
      <dc:creator><![CDATA[Brian Farnhill]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/brian_farnhill/2015/12/21/retrieving-the-url-of-an-app-catalog-site-in-sharepoint-2013/</guid>
      <description><![CDATA[In some of my recent work with the xSharePoint resources we came up with a need to retrieve the...]]></description>
      <content:encoded><![CDATA[<p>In some of my recent work with the xSharePoint resources we came up with a need to retrieve the current URL of an app catalog so that we could determine if the current settings were correct or not. The issue with this is that we found there is no out of the box cmdlet to retrieve the current app catalog URL for a specific web application, only to update the settings with <a href="https://technet.microsoft.com/en-us/library/jj730454.aspx" target="_blank">Update-SPAppCatalogConfiguration</a>. To facilitate getting the current URL we had to do a bit of digging to find where it was stored after this point, and as it turned out this was found in the properties of the app feature which is turned on within the web application object. In order to retrieve the current URL we wrote the following little script to get it:</p>
<pre class="scroll"><code class="csharp"> $WebAppUrl = "https://sharepoint1:1234"<br /> <br /> $wa = Get-SPWebApplication $WebAppUrl<br /> $feature = $wa.Features[[Guid]::Parse("f8bea737-255e-4758-ab82-e34bb46f5828")]<br /> $site = Get-SPSite $feature.Properties["__AppCatSiteId"].Value<br /> <br /> return $site.Url</code></pre>
<p>There are two things to notice here:</p>
<ol>
<li>The GUID we use here is a constant, it's the ID of the feature that is responsible for flagging the app catalogs presence in a web application</li>
<li>The property that stores the GUID of the site collection is called "__AppCatSiteId"</li>
</ol>
<p>This allows us to retrieve the site collection and get the URL that is currently used. Remember if you want to update this that you should use the&nbsp;<a href="https://technet.microsoft.com/en-us/library/jj730454.aspx" target="_blank">Update-SPAppCatalogConfiguration</a> cmdlet I mentioned before as manually manipulating this feature and its properties will get you in an unsupported state pretty quickly - but you can interrogate the values that SharePoint puts here to find the current app catalog URL if you need to read it for something you are working on. </p>]]></content:encoded>
    </item>
  </channel>
</rss>