Azure PowerShell Function App automates Azure Storage file sync to secondary Storage Account.

Azure Storage Files is a sub service of Azure Storage Accounts. It offers fully managed file shares in the cloud that are accessible via the industry standard Server Message Block (SMB) protocol (also known as Common Internet File System or CIFS). The cloud File shares can be mounted concurrently by cloud or on-premises deployments of Windows, Linux, and macOS machines.

At a recent cloud migration engagement, a key requirement was the automation of Azure storage file sync/copy to a secondary storage account in the same Azure subscription to help mitigate against accidental file deletion.

As of this writing, Azure Backup and snapshots are not supported for Azure File Storage. Microsoft recommends using Robocopy, the AzCopy CLI tool or a 3rd party backup tool to copy or backup Azure File storage data. Robocopy needs a UNC path,which will require the Azure Storage File Share be mapped as a local network drive for local access to the files. This option did not meet the requirement for automation. The AzCopy tool, does not require local access to a mapped drive, but needs to be installed on the Azure VM or on-premise server.

The ideal automated solution should be deployed in Azure and in addition, trigger email notifications with details of the copied file storage items after processing the files. I developed the following PowerShell function to meet these requirements. Hopefully, Azure Backup and Snapshot support for Azure File Storage will be rolled out soon. A feature request for File Storage Account backups or snapshots currently exists at this link.

This function (which can also be downloaded at my GitHub Repository) loops through the existing file shares and directories, then copies the existing files based on the last modified time stamp property being greater than one hour. I deployed this solution as a Time triggered PowerShell Azure Function App executed once at the top of every hour.

At this time, I don’t think Azure Function Apps support Azure Key Vault for secure authentication to Azure( I could be wrong though and stand corrected). For this Function App to authenticate with Azure, I generated and uploaded an AES256 Encryption key file used to encrypt/decrypt the password in order to generate an encrypted standard password file to be referenced by the function App. The following screen shot displays the Function App settings for the encrypted Azure logon password, Azure credential username, encrypted sendgridpassword and the local time zone.

<#
.SYNOPSIS
   The Copy-AzureStorageFilesFunctionApp Function copies files from one storage account to another in the same subscription.
.DESCRIPTION
   The Copy-AzureStorageFilesFunctionApp Function copies files from one storage account to another in the same subscription. At this time while developing this function 9/13/2017,
   Azure Backup and snapshots are not supported for Azure File Storage.
   This function is written to be deployed as an Azure Function App and executed based on an hourly time trigger.
   It take a Resource Group name and subscription name as parameters.
   An email notification is sent to the Subscription Admin with a list of the items copied.
.PARAMETER SubscriptionName
        Subscription to be processed.
.PARAMETER ResourceGroupName
        ResourceGroupName of the source and target storage accounts.
.EXAMPLE
        Copy-AzureStorageFilesFunctionApp            
.FUNCTIONALITY
        PowerShell Language
#>
[CmdletBinding()]

param(
    [parameter(Mandatory = $false)]
    [string]$SubscriptionName = "Trial",
    [parameter(Mandatory = $false)]
    [string]$ResourceGroupName = "RGVPN"
       
)

#region Azure Logon
$Username = $env:AzureUsernameCredential
$EncryptedPassword = $env:AzurePasswordCredential
$AESKeyPath = "D:\home\site\wwwroot\TimerTriggerPowerShell1\bin\AESKey.key"
$SecurePassword = ConvertTo-SecureString -String $EncryptedPassword -Key (Get-Content -Path $AESKeyPath)
$Credential = New-Object -TypeName System.Management.Automation.PSCredential($Username, $SecurePassword)
Login-AzureRmAccount -Credential $Credential
Select-AzureRmSubscription -SubscriptionName $SubscriptionName

#endregion

#region create email creds
$Smtpserver = "smtp.sendgrid.net"
$From = "jack.bauer@democonsults.com"
$To = "jack.bauer@democonsults.com"
$Port = "587"
$sendgridusername = "azure_a8a3e2815501214fbc3adf4ad15cc315@azure.com"
$sendgridPassword = $env:SendGridPasswordCredential
$emailpassword = ConvertTo-SecureString -String $sendgridPassword -Key (Get-Content -Path $AESKeyPath)
$emailcred = New-Object System.Management.Automation.PSCredential($sendgridusername, $emailpassword)
#endregion

#Initialize storage variables
$StorageAccountName = "storevpn0518"
$DestStorageAccountName = "store0518"
$DestResourceGroupName = "RGXavier"
$Store = Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName
$Shares = Get-AzureStorageShare -Context $Store.context
$Deststore = Get-AzureRmStorageAccount -ResourceGroupName $DestResourceGroupName -Name $DestStorageAccountName
$Items = @()


foreach ($Share in $Shares) {
    $Directories = Get-AzureStorageFile -Share $Share

    foreach ($Directory in $Directories) {
        if ((Get-AzureStorageShare -Name $Share.Name -Context $Deststore.Context -ErrorAction SilentlyContinue) -eq $null) { 
            
            New-AzureStorageShare -Name $Share.Name -Context $Deststore.Context    
        }
        if ((Get-AzureStorageFile -ShareName $Share.Name -Context $Deststore.Context -Path $Directory.Name -ErrorAction SilentlyContinue) -eq $null) {
            New-AzureStorageDirectory -ShareName $Share.Name -Context $Deststore.Context -Path $Directory.Name               
                
        }
        $sourcefiles = Get-AzureStorageFile -ShareName $Share.Name -Context $Store.context -Path $Directory.Name | Get-AzureStorageFile        
        $destfiles = Get-AzureStorageFile -ShareName $Share.Name -Context $Deststore.Context -Path $Directory.Name -ErrorAction SilentlyContinue `
            | Get-AzureStorageFile
        if ($destfiles.count -eq 0) {
            foreach ($sourcefile in $sourcefiles) {
                Start-AzureStorageFileCopy -SrcShareName $Share.Name -SrcFilePath ($Directory.Name + "/" + $sourcefile.Name)  -Context $Store.Context `
                    -DestShareName $Share.Name -DestFilePath ($Directory.Name + "/" + $sourcefile.Name) -DestContext $Deststore.Context -ErrorAction SilentlyContinue
            }
        }
        else {
            foreach ($sourcefile in $sourcefiles) {
                $sourcefile.FetchAttributes()
                if ($sourcefile.Properties.LastModified.LocalDateTime -gt ((Get-Date).AddMinutes(-60)) ) {
                    Start-AzureStorageFileCopy -SrcShareName $Share.Name -SrcFilePath ($Directory.Name + "/" + $sourcefile.Name)  -Context $Store.Context `
                        -DestShareName $Share.Name -DestFilePath ($Directory.Name + "/" + $sourcefile.Name) -DestContext $Deststore.Context -Force -ErrorAction SilentlyContinue

                    $copyState = Get-AzureStorageFileCopyState -ShareName $Share.Name -FilePath ($Directory.Name + "/" + $sourcefile.Name) -Context $Deststore.Context `
                        -WaitForComplete -ErrorAction SilentlyContinue
                    $Items += $copyState
                }
            }
        }
    }
}

#region process email

$a = "<style>"
$a = $a + "BODY{background-color:white;}"
$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a = $a + "TH{border-width: 1px;padding: 10px;border-style: solid;border-color: black;}"
$a = $a + "TD{border-width: 1px;padding: 10px;border-style: solid;border-color: black;}"
$a = $a + "</style>"
$body = ""
$body += "<BR>"
$body += "These" + " " + ($Items.count) + " " + "file storage items were processed for copy. Thank you."
$body += "<BR>"
$body += "<BR>"
$body += $Items | Select-Object -Property Status, @{"Label" = "Completion Time"; e = {$_.CompletionTime.LocalDateTime}}, @{"Label" = "Item"; e = {$_.Source.AbsolutePath}} | ConvertTo-Html -Head $a
$body = $body |Out-String
$subject = "These items were processed for copy."
if ($Items -ne $null) {
    Send-MailMessage -Body $body `
        -BodyAsHtml `
        -SmtpServer $smtpserver `
        -From $from `
        -Subject $subject `
        -To $to `
        -Port $port `
        -Credential $emailcred `
        -UseSsl
}
#endregion

The following screen shot displays a list of copied files embedded in an email notification after code execution.

Posted in Azure File Storage Copy, Azure Function App | Tagged , , , , , , , , | Leave a comment

Remove Active Directory Domain Services (ADDS) from a WS2012 R2 with PowerShell.

I decided to tear down my Azure Lab IaaS and ASR infrastructure and rebuild it. The process involves removing the ASR configurations, Recovery Vaults, S2S VPNs, VNets and Azure VM running as a Azure based Domain controller for resilience with on-premise infrastructure and Azure deployed Apps. The next steps will include uninstalling the Domain Controller, delete and disable the VMs and Hyper-V Hosts configured for Azure site Recovery. Following these tasks, I’ll delete or remove the resource group. All resources were configured and created within one ARM Resource Group to make it easier to tear down. The following are steps to uninstall ADDS from the Azure VM using PowerShell:

1) Login to the WS2012 R2 Domain Controller.

2) Open a PowerShell console as Admin.

3) Use the Get-Command -Module ADDSDeployment cmdlet to review the necessary ADDSDeployment module commands.

4) psdemote

5) Create a Credential variable:

psdemote2

6) Create a Local Admin Password secure string: $adminPassword = ConvertTo-SecureString -String "pa55w04d123A" -AsPlainText -Force

7) Run the ADDS uninstallation cmdlet with the WhatIf parameter to confirm that the script will run successfully:

Uninstall-ADDSDomainController -LocalAdministratorPassword $adminPassword -Credential $cred -DnsDelegationRemovalCredential $cred -RemoveDnsDelegation -WhatIf

psdemote3

8) Run the actual script without the WhatIf parameter:

psdemote4

9) Remove the ADDS role : Remove-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools -Remove -WhatIf

It might still be necessary to remove refereces to the old DC in DNS and AD Domain Sites.

After disabling protection on the VMs, I deleted the Hyper-V Hosts in the Recovery vault site and then used the following cmdlet to remove the Resource Group:

PS C:\Users\Chinny> Get-AzureRmResourceGroup -Name RGXavier | Remove-AzureRmResourceGroup -Force

Posted in Active Directory, Active Directory Domain Services, Azure, Azure Site Recovery, Azure VPN, DCPromo, Domain Controller, FSMO, Microsoft Hyper-v, PowerShell, Powershell 4.0, Windows Server 2012 R2 | Tagged , , , , , , , , | Leave a comment

Configuring Cisco Virtual Switch System (VSS) on Cisco Catalyst 4500X Switches.

As part of a network infrastructure upgrade at a client site, I’ll be implementing Cisco VSS (Virtual Switching System). This technology will go a long way to meet some of the stated objectives of this infrastructure upgrade which include : Physical Hardware Redundancy, High Availability achieved by Switch Clustering, Self-healing, Increased bandwidth (10 GB trunk), to mention a few.

Quick Background on VSS:
A Virtual switching system (VSS) combines a pair of Catalyst 4500X series switches into a single network component, enabling them to function as one logical switch.Cisco Virtual Switching System is a clustering technology that pools two Cisco Catalyst 4500X Series Switches into a single virtual switch.

In a VSS, the data plane of both clustered switches is active at the same time in both chassis. In my VSS implementation, both VSS Switch members are connected by 2 virtual switch links (VSLs) using 10 Gigabit Ethernet connections between the VSS members. Virtual Switch Links carry regular user traffic in addition to control data between the VSS members.

Prerequisites:

I have outlined my VSS configuration steps for a pair of Cisco Catalyst 4500X switches below starting with Switch-1. The following diagram illustrates the physical topology:

VSS-Schematic

Switch 1 Virtual Domain and Port Channel Configuration:

Switch-1(config)#switch virtual domain 100

Switch-1(config-vs-domain)#switch 1

Switch-1(config-vs-domain)#exit

Switch-1(config)#interface port-channel 10

Switch-1(config-if)#switchport

Switch-1(config-if)#switch virtual link 1

Switch-1(config-if)#no shutdown

Switch-1(config-if)#exit

Configure Virtual Switch Link:

Switch-1(config)#interface range tenGigabitEthernet 1/1-2

Switch-1(config-if)#channel-group 10 mode on

Switch-1(config-if)#no shutdown

Switch-1(config-if)#channel-group 10 mode on
WARNING: Interface TenGigabitEthernet1/1,2 placed in restricted config mode. All extraneous configs removed!

Switch-1(config)#do wr mem (Save the current configuration)

Switch-1(config)#exit

Switch-1#switch convert mode virtual (Execute the command, but do not reload until VSS configuration is completed on Switch 2)

Switch 2 Virtual Domain and Port Channel Configuration:

Switch-2(config)#switch virtual domain 100

Switch-2(config-vs-domain)#switch 2

Switch-2(config-vs-domain)#exit

Switch-2(config)#interface port-channel 20

Switch-2(config-if)#switchport

Switch-2(config-if)#switch virtual link 2

Switch-2(config-if)#no shutdown

Switch-2(config-if)#exit

Configure Virtual Switch Link:

Switch-2(config)#interface range tenGigabitEthernet 1/1-2

Switch-2(config-if)#channel-group 20 mode on

Switch-2(config-if)#no shutdown

Switch-2(config-if)#channel-group 20 mode on
WARNING: Interface TenGigabitEthernet1/1,2 placed in restricted config mode. All extraneous configs removed!

Switch-2(config)#do wr mem (Save the current configuration)

Switch-2(config)#exit

Switch-2#switch convert mode virtual

At this point, console into Switch-1 . You will be prompted to save the work and confirm the switch reboot. Do the same for Switch-2.

After the reboot, verify the VSS configuration:

Switch-1#sh switch virtual

Executing the command on VSS member switch role = VSS Active, id = 1

Switch mode : Virtual Switch
Virtual switch domain number : 100
Local switch number : 1
Local switch operational role: Virtual Switch Active
Peer switch number : 2
Peer switch operational role : Virtual Switch Standby

Executing the command on VSS member switch role = VSS Standby, id = 2

Switch mode : Virtual Switch
Virtual switch domain number : 100
Local switch number : 2
Local switch operational role: Virtual Switch Standby
Peer switch number : 1
Peer switch operational role : Virtual Switch Active
Switch-1#

Verify Dual Active Detection:

Switch-1#sh switch virtual dual-active summary

Executing the command on VSS member switch role = VSS Active, id = 1

Pagp dual-active detection enabled: Yes
FastHello dual-active detection enabled: Yes
In dual-active recovery mode: No

Executing the command on VSS member switch role = VSS Standby, id = 2

Pagp dual-active detection enabled: Yes
FastHello dual-active detection enabled: Yes
In dual-active recovery mode: No

After the VSS configuration and restart, both switches start to function as one. One switch is designated as the Active and the other as the Standby switch. If I attempt to console into the Standby switch and run commands, I get the following prompt:

Switch-1-Standy#sh run
Standby console disabled.
Valid commands are: exit, logout

Configure VSS Cluster Uplink to Distribution Stack Switches as Multichassis EtherChannel (MEC):

VSS enables the creation of Multi-Chassis EtherChannel (MEC), which is an Etherchannel whose member ports are distributed across the member switches in a VSS. The fact that ports from both chassis of the Virtual Switching System are included in this etherchannel makes it a Multichassis EtherChannel (MEC). My MEC configuration steps are below.

Console into the VSS switch cluster. Doesn’t matter if it’s the Switch master or slave, and configure the etherchannel switchport as it would on any other switch(In this scenario, I’m consoled into the standby/slave switch):

Switch-1(config)#interface port-channel 30
Switch-1(config)#switchport
Switch-1(config)# no shut

Add ports from both chassis of the VSS Cluster to the Port Channel:

Switch-1(config)#interface range tenGigabitEthernet 1/1/3, tenGigabitEthernet 2/1/3
Switch-1(config-if)#switchport channel-group 30 mode on
Switch-1(config-if)#no shut

Configure the port channel and the physical ports in the Upstream Distribution Stack Switch:

DataCenterStack(config)#interface port-channel 30
DataCenterStack(config-if)#switchport trunk encapsulation dot1q
DataCenterStack(config-if)#switchport mode trunk
DataCenterStack(config-if)#no shut

Add the Stack switches physical ports to the Channel group. This would be the ports fitted with the X2-10GB-SR fiber converters on the Distribution Layer 3750E-48PD switches as indicated in the diagram above:

DataCenterStack(config)#interface range TenGigabitEthernet1/0/1-2
DataCenterStack(config-if)#switchport trunk encapsulation dot1q
DataCenterStack(config-if)#switchport mode trunk
DataCenterStack(config-if)#channel-group 30 mode on
DataCenterStack(config-if)#exit
DataCenterStack(config)#

Verify Ether Channel configuration :

DataCenterStack#sh etherchannel summary
Flags: D - down P - bundled in port-channel
I - stand-alone s - suspended
H - Hot-standby (LACP only)
R - Layer3 S - Layer2
U - in use f - failed to allocate aggregator

M - not in use, minimum links not met
u - unsuitable for bundling
w - waiting to be aggregated
d - default port

Number of channel-groups in use: 1
Number of aggregators: 1

Group Port-channel Protocol Ports
------+-------------+-----------+-----------------------------------------------
30 Po30(SU) - Te1/0/1(P) Te1/0/2(P)

I configured an SVI (Switch Virtual Interface) for telnet or ssh(preferably) management. I would add that it’s important to verify that the version of Cisco IOS-XE software on both VSS switches is the same.

Posted in Cisco, Virtual Switch System, VSS | Tagged , , , , , , , , , , , , , | Leave a comment