<?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>PFE Field Notes</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/knightly/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/knightly/feed.xml</link>
    <description>Automations, tips and tricks from the field</description>
    <lastBuildDate>Wed, 17 Apr 2019 03:43:03 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Using Azure Automation, OMS and Storage Tables to capture Configuration Data of Azure VMs Part Two</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/using-azure-automation-oms-and-storage-tables-to-capture-configuration-data-of-azure-vms-part-two</link>
      <pubDate>Fri, 12 Jan 2018 18:22:31 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=227</guid>
      <description><![CDATA[Well it’s been a long time since part one of this blog and much has changed with OMS Log Analytics....]]></description>
      <content:encoded><![CDATA[<p><br></p><p>Well it’s been a long time since part one of this blog and much has changed with OMS Log Analytics. Namely, the query language has transitioned to Kusto and the webhook format that OMS sends to Azure Automation has changed.</p><p>The webhook payload change is very different if the Log Analytics workspace has been upgraded to Kusto (or if it is a new workspace). Examples of this payload can be found <a href="https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-alerts-actions#sample-payload" target="_blank">here</a>. The method to consume this webhook data in PowerShell is below. The meat of it is lines 67-83, but just as important (and similar to the old method) is accepting the webhook payload via a PowerShell parameter. </p><div class="wlWriterEditableSmartContent" id="scid:C89E2BDB-ADD3-4f7a-9810-1B7EACF446C1:e7addac1-831d-4001-a43c-2b7ef8489dbb" style="margin: 0px;padding: 0px;float: none"><pre>
[sourcecode language='powershell'  padlinenumbers='true' wraplines='false']
&lt;# 
 
     .SYNOPSIS 
  
         vmcreate_v2.0.ps1 is an Azure Automation Powershell Runbook 
       .DESCRIPTION 
  
     This script recieves webhook data from OMS based on Azure Activity Logs recording a VM create 
     It will record the basic CMDB data and write it to the automation account output and to an azure storage table with the write-cmdbdata function     
 
 
    .EXAMPLE 
          This should be called by OMS based on an activity log search. See blogs.technet.microsoft.com/knightly 
 
     
 
    .NOTES 
  v 1.2 checks for vnet peering and only writes to the table if the vnet is peered. It also records the source image name.
  v 2.0 is updated for new webhook format of an array of tables containing the vm information 
   #&gt; 


param ( 
    [object]$WebhookData
)

$RequestBody = ConvertFrom-JSON -InputObject $WebhookData.RequestBody
$connectionName = &quot;AzureRunAsConnection&quot;
try
{
    # Get the connection &quot;AzureRunAsConnection &quot;
    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         

    &quot;Logging in to Azure...&quot;
    Add-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection.TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
}
catch {
    if (!$servicePrincipalConnection)
    {
        $ErrorMessage = &quot;Connection $connectionName not found.&quot;
        throw $ErrorMessage
    } else{
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}
 
#Get all metadata properties    
$AlertRuleName = $RequestBody.AlertRuleName
$AlertThresholdOperator = $RequestBody.AlertThresholdOperator
$AlertThresholdValue = $RequestBody.AlertThresholdValue
$AlertDescription = $RequestBody.Description
$LinktoSearchResults =$RequestBody.LinkToSearchResults
$ResultCount =$RequestBody.ResultCount
$Severity = $RequestBody.Severity
$SearchQuery = $RequestBody.SearchQuery
$WorkspaceID = $RequestBody.WorkspaceId
$SearchWindowStartTime = $RequestBody.SearchIntervalStartTimeUtc
$SearchWindowEndTime = $RequestBody.SearchIntervalEndtimeUtc
$SearchWindowInterval = $RequestBody.SearchIntervalInSeconds

# Get detailed search results
if($RequestBody.SearchResult -ne $null)
{
    $SearchResultRows    = $RequestBody.SearchResult.tables[0].rows 
    $SearchResultColumns = $RequestBody.SearchResult.tables[0].columns;

    foreach ($SearchResultRow in $SearchResultRows)
    {   
        $Column = 0
        $Record = New-Object -TypeName PSObject 

        foreach ($SearchResultColumn in $SearchResultColumns)
        {
            $Name = $SearchResultColumn.name
            $ColumnValue = $SearchResultRow[$Column]
            $Record | Add-Member -MemberType NoteProperty -Name $name -Value $ColumnValue -Force
            $Column++
        }
$resourceID = $record.resourceID
$vmname = $record.resource
$rgname = $record.resourcegroup
$subID = $record.subscriptionID 
$caller = $record.caller
write-output $vmname + &#039;in resource group &#039; + $rgname &#039;in sub&#039; + $subID + &#039;was created&#039;
Select-AzureRmSubscription -SubscriptionId $SubId
$vminfo = Get-AzureRmvm -Name $vmname -ResourceGroupName $Rgname
$vmsize = $vminfo.HardwareProfile.vmsize
$nic = $vminfo.NetworkProfile.NetworkInterfaces
$string = $nic.id.ToString()
$nicname = $string.split(&quot;/&quot;)[-1]
$ipconfig = Get-AzureRmNetworkInterface -ResourceGroupName $rgname -Name $nicname
$subnet = $ipconfig.ipconfigurations.subnet.id.ToString()
$ipconfig = $ipconfig.IpConfigurations.privateipaddress
$vnet = $subnet.split(&quot;/&quot;)[-3]
$name = $vminfo.Name
$ostype = $vminfo.StorageProfile.OsDisk.OsType 
$location = $vminfo.location

#imageref is null if marketplace image was used
$imageref = $vminfo.StorageProfile.ImageReference.id
            if ($imageref -ne $null)
                {$sourceimg = $imageref.Split(&quot;/&quot;)[-1]}
                   else {$sourceimg = &#039;marketplace&#039;}
$subname = (Get-AzureRmSubscription -SubscriptionId $subid).SubscriptionName

            
#check to see if the VM is on an ER conencted VNET by checking its peering
$peer=Get-AzureRmVirtualNetwork -Name $vnet -ResourceGroupName $rgname
$peer= $peer.VirtualNetworkPeerings

if ($peer.count -gt 0) #only write this data to the storage table if the network is peered (only peer&#039;d vnets can talk to ER vnets)
  {
      
 

   
    #writing output into the automation account for debugging
    write-output &quot;$vmsize $Ipconfig $location $name $ostype $caller $timestamp $subname, $Vnet, $LID&quot;

    #once VM information is collected, it can be written into a storage table 
    Select-AzureRmSubscription -SubscriptionName &#039;Sub1&#039; #this should be the subscription that owns the storage account, not where the VM is deployed
    $resourceGroup = &quot;RGNAME&quot; #resource group that contains the storage table
    $storageAccount = &quot;cmdbtable&quot; #storage account that contains the table
    $tableName = &quot;CMData&quot;
    $saContext = (Get-AzureRmStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccount).Context
    $table = Get-AzureStorageTable -Name $tableName -Context $saContext 

 
    #search the storage table to see if the VM already exists
    [string]$filter1 = [Microsoft.WindowsAzure.Storage.Table.TableQuery]::GenerateFilterCondition(&quot;ResourceID&quot;, [Microsoft.WindowsAzure.Storage.Table.QueryComparisons]::Equal, &quot;$resourceID&quot;)
    $new = Get-AzureStorageTableRowByCustomFilter -table $table -customFilter $filter1 
    if ($new -eq $null) {
        $partitionKey = &quot;VMcreates&quot;
        Add-StorageTableRow -table $table -partitionKey $partitionKey -rowKey ([guid]::NewGuid().tostring()) -property @{&quot;SourceIMG&quot; = &quot;$sourceIMG&quot;; &quot;SubscriptionID&quot; = &quot;$subid&quot;; &quot;ResourceGroup&quot; = &quot;$rgname&quot;; &quot;ResourceID&quot; = &quot;$resourceID&quot;; &quot;computerName&quot; = &quot;$vmname&quot;; &quot;ostype&quot; = &quot;$ostype&quot;; &quot;CreatorID&quot; = &quot;$caller&quot;; &quot;PrivateIP&quot; = &quot;$IPconfig&quot;; &quot;Vnet&quot;=&quot;$Vnet&quot;;&quot;Location&quot; = &quot;$Location&quot;}
          
    }
    else {
        $partitionKey = &quot;VMUpdates&quot;
        Add-StorageTableRow -table $table -partitionKey $partitionKey -rowKey ([guid]::NewGuid().tostring()) -property @{&quot;SourceIMG&quot; = &quot;$sourceIMG&quot;; &quot;SubscriptionID&quot; = &quot;$subid&quot;; &quot;ResourceGroup&quot; = &quot;$rgname&quot;; &quot;ResourceID&quot; = &quot;$resourceID&quot;; &quot;computerName&quot; = &quot;$vmname&quot;; &quot;ostype&quot; = &quot;$ostype&quot;; &quot;CreatorID&quot; = &quot;$caller&quot;; &quot;PrivateIP&quot; = &quot;$IPconfig&quot;;&quot;Vnet&quot;=&quot;$Vnet&quot;; &quot;Location&quot; = &quot;$Location&quot;}
          
    }

}}  }


  
[/sourcecode]
</pre>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Find Azure VMs that are "Shut Down" but not "Deallocated"</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/find-azure-vms-that-are-shut-down-but-not-deallocated</link>
      <pubDate>Fri, 13 Oct 2017 21:17:05 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=215</guid>
      <description><![CDATA[As you likely know, if you shut down an azure VM from the operating system, Azure does not...]]></description>
      <content:encoded><![CDATA[As you likely know, if you shut down an azure VM from the operating system, Azure does not deallocate resources to that VM, and therefor you are still being charged for those resources. In order to get the VM to stop billing, it has to be in the 'deallocated' state. In order to deallocate, Azure must be used to shut down the VM, not the OS (you can use the portal, CLI or PowerShell). Below is a powershell script to find virtual machines that are shut down but not deallocated, check to see if it has a static IP (VMs release their IP when deallocated) and if it does not have a static public or private IP, deallocate the VM. GitHub link is <a href="https://github.com/jknightly/monitoring/blob/master/deallocate" target="_blank">HERE</a>

<code>
 

#Login-AzureRmAccount 
$rg = get-azurermresourcegroup
foreach ($r in $rg){
    $vm = Get-AzureRmVM -ResourceGroupName $r.resourcegroupname -Status | Where-Object {$_.powerstate -like 'VM stopped'} 
    foreach ($v in $VM)
        {$nic = $v.NetworkProfile.NetworkInterfaces
        $string = $nic.id.ToString()
        $nicname = $string.split("/")[-1]
        $ipconfig = Get-AzureRmNetworkInterface -ResourceGroupName $r.ResourceGroupName -Name $nicname 
        $pip = $ipconfig.IpConfigurations.publicipaddress.Id
        if ($pip -ne $null) 
            {$pipname = $pip.split("/")[-1]
            $pip = Get-AzureRmPublicIpAddress -Name $pipname -ResourceGroupName $r.ResourceGroupName
            $pip = $pip.PublicIpAllocationMethod}
        if (($ipconfig.IpConfigurations.privateIPAllocationMethod -like "Static") -and ($pip -ne 'Dynamic' -or $pip -eq $null)) 
            {stop-azurermvm -resourcegroupname $r.ResourceGroupName -Name $v.name -Force
            write-output "Stopping VM" $v.name}
        Else {write-output $v.name "Has Dynamic IP and Can't be deallocated without releasing the IP. Please use the portal to shutdown after confirming that releasing the IP is OK"}
        }
        }


   
</code>]]></content:encoded>
    </item>
    <item>
      <title>No Extensions! Getting Basic Azure VM Metric Data</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/no-extensions-getting-basic-azure-vm-metric-data</link>
      <pubDate>Thu, 28 Sep 2017 21:07:36 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=195</guid>
      <description><![CDATA[If you need to grab some basic performance metrics on an Azure Virtual Machine that doesn't have...]]></description>
      <content:encoded><![CDATA[If you need to grab some basic performance metrics on an Azure Virtual Machine that doesn't have either the OMS or Azure Monitor extensions, it can still be done with the metrics here: <img src="https://jkblogimages.blob.core.windows.net/blogimages/basicmetrics.JPG" alt="Basic Metrics" />

You can then use the GUI to create alerts (which this blog won't cover) or if you have 100's of VMs to set this on, use PowerShell! In this use case, the virtual machines that are using less than 25% CPU need to be discovered so they can be sized down. This alert could call a webhook (which could call an Azure Automation, which is covered in previous blog posts), but for this example, it's just email.

The script checks to see if the VM is running and if it is, sets an alert to send an email if the CPU is under 25% for 1 day. The script can be found <a href="https://github.com/jknightly/monitoring/blob/master/basicCPU.ps1" target="_blank">here</a>

The version as of this writing is below:
<code>
Login-AzureRmAccount
Select-AzureRmSubscription -SubscriptionName 'subscriptionName'
$vm = Get-AzureRMVm
$actionEmail = New-AzureRmAlertRuleEmail -CustomEmail none@none.com
foreach ($v in $vm)
{$status = Get-AzureRmVM -ResourceGroupName $v.ResourceGroupName -Name $v.Name -Status
    if ($status.Statuses[1].DisplayStatus -contains 'VM Running'){
    Add-AzureRmMetricAlertRule -Name vmcpu_lt_25 -Location $v.location -ResourceGroup $v.resourcegroupname -TargetResourceId $v.Id -MetricName "Percentage CPU" -Operator LessThan -Threshold 25 -WindowSize 01:00:00 -TimeAggregationOperator Average -Actions $actionEmail -Description "alert on CPU &lt; 25%&quot;
    }   
}
</code>]]></content:encoded>
    </item>
    <item>
      <title>Customizing, Exporting and Importing Azure VM Diagnostic Settings</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/customizing-exporting-and-importing-azure-vm-diagnostic-settings</link>
      <pubDate>Wed, 06 Sep 2017 00:51:18 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=176</guid>
      <description><![CDATA[Azure VM Diagnostics on Windows VMs can track more than just the basic performance counters. Through...]]></description>
      <content:encoded><![CDATA[Azure VM Diagnostics on Windows VMs can track more than just the basic performance counters. Through the Azure portal UI as in the image below you can add performance counters that are available to the operating system. Inside of windows run "Typeperf -q &gt; counters.txt" at a command prompt to get a txt file of all the available counter names, then add the seem as seen here: 
<img src="https://jkblogimages.blob.core.windows.net/blogimages/activesessions.JPG" alt="Performance Counters" />

Once you have added all of the custom performance counters you want, you can use the powershell below to export the configuration to a JSON file, then apply to existing or new VMs. The PowerShell below assumes an existing virtual machine with no azure diagnostics extension installed. It would be fairly simple to add a check to see if extensions are installed and remove/re-add with the imported config as well as use powershell to update the json file for new diagnostic storage account locations and resourceIDs.

<code>
Login-AzureRmAccount
#source resource group and virtual machine to take the diagnostic settings from.
$rg = 'resourcegroup1
$vm = 'vm1'
$diag = (Get-AzureRmVMDiagnosticsExtension -ResourceGroupName $rg -VMName $vm).publicsettings
$path = 'c:\users\joknight\diag.json'
$diag | out-file $path
$targetVM='targetvm'
$targetRG='targetrg'
#you may want to edit the diagnostics storage account in the diag.json file
#be sure to edit the resourceID in the diag.json file to match the target VM
Set-AzureRmVMDiagnosticsExtension -ResourceGroupName $targetrg -VMName $targetvm -DiagnosticsConfigurationPath $path

</code>
]]></content:encoded>
    </item>
    <item>
      <title>Using Azure Automation, OMS and Storage Tables to capture Configuration Data of Azure VMs Part One.</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/using-azure-automation-oms-and-storage-tables-to-capture-configuration-data-of-azure-vms-part-one</link>
      <pubDate>Wed, 23 Aug 2017 00:47:36 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=145</guid>
      <description><![CDATA[Many customers have legacy configuration management database (CMDB) systems that they'd like to...]]></description>
      <content:encoded><![CDATA[Many customers have legacy configuration management database (CMDB) systems that they'd like to update with Azure Virtual Machine configuration data like hostname, IP address, subscription, etc. Part one of this blog will review how to capture this information to an Azure Storage table each time a virtual machine is created. Subsequent parts will cover virtual machine changes and deletes.

At a high level:
1. Azure Activity Logs are being sent to OMS Log Analytics
2. OMS sends an Alert Webhook to Azure Automation on each vm creation event
3. Azure Automation executes a runbook that captures the VM information and writes into Azure Storage Tables
4. An on-premises process picks up the data and writes it into the legacy CMDB (this part is not covered in this or subsequent blog posts).

The first step is well documented <a href="https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-activity">here </a>so I won't belabor the point.

The next step is also well documented. Remember that your Azure Automation Account and OMS Workspace must be in the same resource group.
<a href="https://docs.microsoft.com/en-us/azure/automation/automation-invoke-runbook-from-omsla-alert">Link</a>

Step three requires a few things. First, the Azure Automation must parse the JSON it is receiving from OMS/Webhook. My coworker Rob Davies has an excellent blog post about this process <a href="https://blogs.technet.microsoft.com/robdavies/2016/06/24/ingest-azure-diagnostic-logging-in-log-analytics-oms-part-2/" target="_blank">here</a> and you can also see the example I've used in the runbook. Once the runbook has parsed the JSON it needs to get the configuration information for the VM - this uses regular Azure PowerShell like get-azurermvm. Finally, the runbook needs to write this data to an Azure Storage Table. For this I leaned (or plagiarized?) another coworker Paulo Marques da Costa who wrote a very handy Powershell module for working with <a href="https://blogs.technet.microsoft.com/paulomarques/2017/01/17/working-with-azure-storage-tables-from-powershell/">tables</a>. This module is available in the gallery and must be imported into the Automation Account. I would recommend updating all PowerShell modules in the automation account as well as adding the AzureRM.Network module.

You'll notice a couple odd logic sections in the runbook as well. The first "foreach ($v in $cmdata.searchresults.value)" is there because OMS only alerts once every five minutes. If you create 10 VMs in 2 minutes, they'll come through in a single alert with multiple values in the request body.

The next logic workaround is because there may be several virtual machine write operations in the activity log on a single VM creation operation. The logic after the comment "#search the storage table to see if the VM already exists" checks to see if the resourceID is already in the storage table, and if so, records it as a VM Update. 

This is definitely a continuing effort so I'm not posting the PowerShell here as it'll see quite a few updates. The runbook solution can be found on <a href="https://github.com/jknightly/cmdb" target="_blank">GitHub</a>. 



]]></content:encoded>
    </item>
    <item>
      <title>Pass-the-Hash Lab</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/pass-the-hash-lab</link>
      <pubDate>Tue, 18 Jul 2017 21:37:31 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=126</guid>
      <description><![CDATA[If you haven't seen how a PtH attack works first hand, I highly recommend this lab. It walks through...]]></description>
      <content:encoded><![CDATA[If you haven't seen how a PtH attack works first hand, I highly recommend this lab. It walks through using mimikatz to use an NTLM hash to authenticate to a domain controller, then uses the PtH mitigations in Server 2016 like credential guard to defend.

https://blogs.technet.microsoft.com/datacentersecurity/2017/05/15/credential-guard-lab-companion/
]]></content:encoded>
    </item>
    <item>
      <title>Using Azure Automation with Multiple Subscriptions</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/using-azure-automation-with-multiple-subscriptions</link>
      <pubDate>Fri, 26 May 2017 18:14:02 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=95</guid>
      <description><![CDATA[Azure Automation is very useful for almost every Azure administrator, but it can be challenging to...]]></description>
      <content:encoded><![CDATA[Azure Automation is very useful for almost every Azure administrator, but it can be challenging to figure out how to run a process across multiple subscriptions. Below we'll walk through the steps to run a PowerShell run book across multiple subscriptions with the assumption that the automation account already exists in one subscription. For information on how to create an automation account, see <a href="https://docs.microsoft.com/en-us/azure/automation/automation-create-standalone-account">here</a> . The other assumption is that we are working with Resource Manager, not Classic deployments.

Once we have our automation account the high level steps are as follows:
1. Find the ApplicationID of the RunAS connection
2. Grant that applicationID the appropriate permissions in the other subscriptions (in my example, I'm giving it subscription level contributor access).
3. Set the appropriate context during runbook execution.

When an Azure Automation account is created it asks if "RunAS" account should be created and defaults to 'yes'. If you keep the defaults, you'll have a connection asset in the automation account called "AzureRunAsConnection". This connection asset is really a Service Principal in Azure AD.
<img src="https://knightlydemo.blob.core.windows.net/image/runas.png" alt="RunAs Connection" />

Copy the "ApplicationID" of the AzureRunAsConnection to the clipboard and grant it rights to any subscriptions you want to manage with your central automation account. In this example, I've given it contributor rights at the subscription level, but you could certainly use more granular permissions here or scope it to a specific resource group.

<img src="https://knightlydemo.blob.core.windows.net/image/sprights.png" alt="Automation Account Subscription Level Permissions" />

Now I just need to write my runbooks in such a way that they'll use the connection asset and select the appropriate subscription. The following code sample runs from a subscription called 'sub1' and creates a new resource group in subscription name 'sub2'. You'll need to update the certificate thumbprint in this script by retrieving it from the Assets--&gt;Certificates blade in the automation account.

<pre><code>

 $Conn = Get-AutomationConnection -Name AzureRunAsConnection 
add-AzureRmAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint #yourthumbprint

Select-AzureRmSubscription -SubscriptionName 'sub2'
$rg = 'testrg'
New-AzureRmResourceGroup -Name $rg -Location westus2
</code>

    





]]></content:encoded>
    </item>
    <item>
      <title>RDP not working over Azure S2S VPN with Palo Alto</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/rdp-not-working-over-azure-s2s-vpn-with-palo-alto</link>
      <pubDate>Fri, 26 May 2017 02:06:36 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=85</guid>
      <description><![CDATA[I ran into an interesting issue with a customer this week. We were deploying a route based VPN from...]]></description>
      <content:encoded><![CDATA[I ran into an interesting issue with a customer this week. We were deploying a route based VPN from Azure Resource Manager to the customer's Palo Alto PA-5020 running PANOS 7.1.8. We were able to stand up the VPN tunnel easily enough but we could not RDP to VMs running in Azure using the VPN while RDP using the public IP worked. We could open port 3389 via a telnet command, but RDP would time out. When running a network trace we saw a fair amount of re-transmits so as a troubleshooting step we lowered the MTU to account for VPN over-head. This didn't help for our use case. The fix turned out to be selecting "Enable NAT Traversal" (the VPN device was not behind a NAT) on the IKE gateway.

MTU Setting:
<img src="https://knightlydemo.blob.core.windows.net/image/mtu2.png" alt="MTU Setting" />

NAT Setting:
<img src="https://knightlydemo.blob.core.windows.net/image/mtu.jpg" alt="NAT Setting" />

We don't (yet) have a good reason as to why this setting solved the RDP issue. I'll comment on this when I know, and if you have some ideas, please feel free to comment.]]></content:encoded>
    </item>
    <item>
      <title>Greenfield Test Environments in Azure</title>
      <link>https://docs.microsoft.com/archive/blogs/knightly/greenfield-test-environments-in-azure</link>
      <pubDate>Wed, 17 May 2017 19:57:18 GMT</pubDate>
      <dc:creator><![CDATA[John Knightly]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/knightly/?p=35</guid>
      <description><![CDATA[Most IT pros will use and re-use environments for testing anything and everything as well as...]]></description>
      <content:encoded><![CDATA[Most IT pros will use and re-use environments for testing anything and everything as well as learning new technologies. Often, building a lab from scratch with AD, Exchange, maybe some other forests, etc can be a very time consuming task.

Azure gives us the ability to build a lab once with all the components we generally need, store the VHDs in the cloud and re-deploy the lab infinitely many times back to it's original state.  Yes, Azure dev/test labs is a feature that can do this at larger scale...this example is for the individual IT pro that owns an Azure subscription, not corporate level testing.

The high level steps are:
<ol>
 	<li>Get an Azure subscription (MSDN is a great solution here - <a href="https://azure.microsoft.com/en-us/pricing/member-offers/msdn-benefits-details/">https://azure.microsoft.com/en-us/pricing/member-offers/msdn-benefits-details/)</a></li>
 	<li>build the lab VMs in Azure (leave yourself a password or two set to never expire - you may want to greenfield this lab in a year!)</li>
 	<li>shut down the lab VMs so you can re-deploy off their VHDs</li>
 	<li>Use an ARM template like this one https://azure.microsoft.com/en-us/resources/templates/101-vm-from-user-image/a PowerShell script, or a combination of the two (like I have sampled below).</li>
 	<li>Once you are done working in the lab, delete the resource group (using my script example the resource group is 'greenfieldlab') and that will delete all of the VMs and storage.</li>
 	<li>Re-deploy the lab as often as needed for new testing. To update the source lab, just start and update the source VMs/VHDs.</li>
</ol>
&nbsp;
<pre><code>

    This code is Copyright (c) 2017 Microsoft Corporation.
All rights reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED To THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.'

IN NO EVENT SHALL MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS CODE OR INFORMATION.

.SYNOPSIS

Script deploys a test lab including 7 VMs.
.DESCRIPTIONin

.PARAMETER

.INPUTS
The script will ask for credentials. These are the credentials for your azure subscription, not the VMs. VM creds are already set.

.NOTES
Version: 1.3
Author: John Knightly Microsoft
Creation Date: 4/25/17

#change this to the path of your template deployment file
$templatefile= 'https://mystorageaccountname.blob.core.windows.net/json/lab.json'

#get credential information
login-azurermaccount

#create resourcegroup
$region = 'westus2'
$RG='GreenfieldLab'
New-AzureRmResourceGroup -Name $RG -Location $region

#create vnet
$subnet1 = new-azurermvirtualnetworksubnetconfig -name 'subnet1' -addressprefix '10.6.0.0/24'
$subnetname='subnet1'
$vnetname=$rg+'vnet'

$VNET = New-AzureRmVirtualNetwork -Name $vnetname -Location $region -ResourceGroupName $RG -AddressPrefix '10.6.0.0/24' -subnet $subnet -DnsServer '10.6.0.4'

#create new storage account, get the access key and create a storage context
$saprefix=get-random -Minimum 1000 -Maximum 10000000
$saname = 'greenfieldlab'+$saprefix
$storage = New-AzureRMStorageAccount -ResourceGroupName $RG -Name $saname -Location $region -type Standard_LRS
$storagekey = (get-azureRMstorageaccountkey -resourcegroupname $RG -StorageAccountName $saname).value[1]
$destcontext = New-AzureStorageContext -StorageAccountName $saname -StorageAccountKey $storagekey
$destcontainer = New-AzureStorageContainer -Name 'images' -Context $destcontext
$destvhd = New-AzureStorageContainer -Name 'vhds' -Context $destcontext

#setup vars to copy files from source to newly created storage account
$sourcesaname = 'yourstorageaccountname'
#use your own source key
$sourcekey= 'youcanthavemystorageaccountkey_gogenerateyourown'
$sourcecontext = New-AzureStorageContext -StorageAccountName $sourcesaname -StorageAccountKey $sourcekey
$sourcecontainer = Get-AzureStoragecontainer -Context $sourcecontext
$blobs = get-azurestorageblob -container $sourcecontainer.Name -Context $sourcecontext

#file copy
foreach ($b in $blobs)
{
Start-AzureStorageBlobCopy -SrcContainer $sourcecontainer.name -DestContainer $destcontainer.name -Context $sourcecontext -DestContext $destcontext -SrcBlob $b.Name

}

write-host "Copy could take some time please be patient. Generally 1-2 hours is normal, but there is no SLA in Azure for storage account copies" -ForegroundColor yellow
# below status checks are functioning to hold script until all blobs are completed copying
$_copytime = get-date
$Blobs | foreach {
while ((get-azurestorageblob $_.name -context $destcontext -Container $destcontainer.name | get-azurestorageblobcopystate).Status -eq "pending"){
$_results = get-azurestorageblob $blobs[1].name -context $destcontext -Container $destcontainer.name | get-azurestorageblobcopystate
Write-Progress -Activity "Copying Blobs" -Status "Progress: $(($_results).bytescopied) of $(($_results).TotalBytes) - Total Time Passed: $((new-TimeSpan($_copytime) $(Get-Date)).minutes) minutes" -PercentComplete ($(($_results).bytescopied)/$(($_results).TotalBytes)*100)
start-sleep -Seconds 60
}
}

write-host "Total copy time $((new-TimeSpan($_copytime) $(Get-Date)).minutes) minutes" -foregroundcolor yellow

#set vars for path of VHD file for each vm to be deployed
$contosoVHD = $destcontext.BlobEndPoint + 'images/contosodc120170331122936.vhd'
$eudc1vhd = $destcontext.BlobEndPoint + 'images/eucontosodc120170417083825.vhd'
$fabdcvhd = $destcontext.BlobEndPoint + 'images/fabrikamdc120170331123706.vhd'
$exchvhd = $destcontext.BlobEndPoint + 'images/contosoex120170331131217.vhd'
$contosofsvhd = $destcontext.BlobEndPoint + 'images/contosofs120170331123927.vhd'
$fabfsvhd = $destcontext.BlobEndPoint + 'images/fabrikamFS120170331130906.vhd'
$eufsvhd = $destcontext.BlobEndPoint + 'images/EUFS120170331131044.vhd'
$templatefile= 'https://812231westus2.blob.core.windows.net/json/lab.json'

#deployment of contosodc1 waiting for this one to deploy first since it is vnets DNS server
New-AzureRmResourceGroupDeployment -Name 'contosodc1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'contosodc1' -ostype 'Windows' -osdiskvhduri $contosovhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname

#wait for contosodc1 deployment to be complete
$contosodcstatus = Get-AzureRmVM -Name contosodc1 -resourcegroup aclxraylab
while ($contosodcstatus.ProvisioningState -ne 'Succeeded')
{start-sleep -seconds 10
$contosodcstatus = Get-AzureRmVM -Name contosodc1 -resourcegroup aclxraylab
write-host 'Waiting for Forest Root DC deployment to complete'
}
Start-Sleep -Seconds 15

#set contosodc1 to static IP
$nic=Get-AzureRmNetworkInterface -Name contosodc1 -ResourceGroupName $rg
$nic.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$nic.IpConfigurations[0].PrivateIpAddress = "10.6.0.4"
Set-AzureRmNetworkInterface -NetworkInterface $nic

#deploy remaining VMs
New-AzureRmResourceGroupDeployment -Name 'eucontosodc1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'eucontosodc1' -ostype 'Windows' -osdiskvhduri $eudc1vhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'fabrikamdc1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'fabrikamdc1' -ostype 'Windows' -osdiskvhduri $fabdcvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'contosoex1' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'contosoex1' -ostype 'Windows' -osdiskvhduri $exchvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'contosofs' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'contosofs1' -ostype 'Windows' -osdiskvhduri $contosofsvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'fabrikamfs' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'FabrikamFS' -ostype 'Windows' -osdiskvhduri $fabfsvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname
New-AzureRmResourceGroupDeployment -Name 'eufs' -ResourceGroupName $RG -TemplateUri $templatefile -vmName 'eufs1' -ostype 'Windows' -osdiskvhduri $eufsvhd -vmsize 'standard_d2_v2' -existingvirtualnetworkname $vnetname -existingvirtualnetworkresourcegroup $RG -subnetname $subnetname

#stop the environment so i'm not getting charged for compute while waiting to start using it
Get-AzureRmvm -ResourceGroupName $rg | stop-AzureRmVM -force

Write-host "Lab deployment is now complete. The VMs are stopped." -ForegroundColor Cyan

]]></content:encoded>
    </item>
  </channel>
</rss>