0004-New-AzureRmVmAvailabilitySetPsg.ps1
<#PSScriptInfo
.VERSION 1.0 .GUID 01c58ba7-37f8-40de-98fb-1495ea5b27dd .AUTHOR Preston K. Parsard .COMPANYNAME Microsoft .COPYRIGHT Copyright (c) 2016 Preston K. Parsard .TAGS Azure, Deploy, VM .LICENSEURI https://opensource.org/licenses/MIT .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES Initial Release .DESCRIPTION This script creates an availability set of 1-4 Windows Server 2016 VMs with Network Security Groups for RDP access #> <# **************************************************************************************************************************************************************************** SYNOPSIS: Creates a new lab of 1-4 VMs and associated resources using the ARM deployment model. DESCRIPTION : This script deploys a set of 1-4 Windows Server 2016 TP5 servers as part of an availability set that can be used to create DCs. This script can be used to demonstrate how PowerShell can be used to imperatively create resources in Azure as part of an initial process of building a functional environment consisting of compute, storage and netorking components. Since this script will be used primarily for demonstration purposes, comments, logging and verbose console output has been included. REQUIREMENTS: PowerShell Version 4.0 LIMITATIONS : TBD AUTHOR(S): Preston K. Parsard prestopa@microsoft.com EDITOR(S): Preston K. Parsard prestopa@microsoft.com REFERENCES: 1. https://gallery.technet.microsoft.com/scriptcenter/Build-AD-Forest-in-Windows-3118c100 2. http://blogs.technet.com/b/heyscriptingguy/archive/2013/06/22/weekend-scripter-getting-started-with-windows-azure-and-powershell.aspx 3. http://michaelwasham.com/windows-azure-powershell-reference-guide/configuring-disks-endpoints-vms-powershell/ 4. http://blog.powershell.no/2010/03/04/enable-and-configure-windows-powershell-remoting-using-group-policy/ 5. http://azure.microsoft.com/blog/2014/05/13/deploying-antimalware-solutions-on-azure-virtual-machines/ 6. http://blogs.msdn.com/b/powershell/archive/2014/08/07/introducing-the-azure-powershell-dsc-desired-state-configuration-extension.aspx 7. http://trevorsullivan.net/2014/08/21/use-powershell-dsc-to-install-dsc-resources/ 8. http://blogs.msdn.com/b/powershell/archive/2014/07/21/creating-a-secure-environment-using-powershell-desired-state-configuration.aspx 9. http://blogs.technet.com/b/ashleymcglone/archive/2015/03/20/deploy-active-directory-with-powershell-dsc-a-k-a-dsc-promo.aspx 10.http://blogs.technet.com/b/heyscriptingguy/archive/2013/03/26/decrypt-powershell-secure-string-password.aspx 11.http://blogs.msdn.com/b/powershell/archive/2014/09/10/secure-credentials-in-the-azure-powershell-desired-state-configuration-dsc-extension.aspx 12.http://blogs.technet.com/b/keithmayer/archive/2014/10/24/end-to-end-iaas-workload-provisioning-in-the-cloud-with-azure-automation-and-powershell-dsc-part-1.aspx 13.http://blogs.technet.com/b/keithmayer/archive/2014/07/24/step-by-step-auto-provision-a-new-active-directory-domain-in-the-azure-cloud-using-the-vm-agent-custom-script-extension.aspx 14.https://blogs.msdn.microsoft.com/cloud_solution_architect/2015/05/05/creating-azure-vms-with-arm-powershell-cmdlets/ KEYWORDS: Mnemonic; [R]esilient<[R]esource Group> [S]ervers<[S]torage Account> [N]eed<Virtual [N]etwork> [V]irtual Machines<[VMs] with [N]etworks<[N]etwork Security Groups> and [A]vailability Sets<[A]vailability Sets> LICENSE: The MIT License (MIT) Copyright (c) 2016 Preston K. Parsard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. DISCLAIMER: THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE 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. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code. **************************************************************************************************************************************************************************** #> <# WORK ITEMS TASK-INDEX: #> <# *************************************************************************************************************************************************************************** REVISION/CHANGE RECORD -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- DATE VERSION Name CHANGE -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 26 APR 2016 00.00.0001 Preston K. Parsard Initial release 30 JUN 2016 00.00.0002 Preston K. Parsard Updated script file name, plus minor edits; removed non-functional comments 11 JUL 2016 00.00.0003 Preston K. Parsard Added code to select subscription based on matching subscription ID of subscription name provided in response to prompt 11 JUL 2016 00.00.0004 Preston K. Parsard Updated script to use external PSGallery module for logging functions and enhanced logging activity 11 JUL 2016 01.00.0000 Preston K. Parsard Applying the verbose common parameter where possible for more details 14 JUL 2016 01.00.0001 Preston K. Parsard Added reference for WriteToLogs module (https://www.powershellgallery.com/packages/WriteToLogs/1.0.19) 14 JUL 2016 01.00.0002 Preston K. Parsard Applied PSScriptInfo to header for publishing to PSGallery #> # Resets profiles in case you have multiple Azure Subscriptions and connects to your Azure Account [Uncomment if you haven't already authenticated to your Azure subscription] Clear-AzureProfile -Force Login-AzureRmAccount # If the WriteToLogs module doesn't already exist, install and import it for use later in the script for logging operations If (!(Get-Module -Name WriteToLogs)) { # https://www.powershellgallery.com/packages/WriteToLogs/1.0.19 Install-Module -Name WriteToLogs -Verbose Import-Module -Name WriteToLogs -Verbose } #end If #region INITIALIZE VALUES $BeginTimer = Get-Date -Verbose Do { # Subscription name (Get-AzureRmSubscription).SubscriptionName [string] $Subscription = Read-Host "Please enter your subscription name [MSDN] " $Subscription = $Subscription.ToUpper() } #end Do Until (($Subscription) -ne $null) # Selects subscription based on subscription name provided in response to the prompt above Select-AzureRmSubscription -SubscriptionId (Get-AzureRmSubscription -SubscriptionName $Subscription).SubscriptionId Do { # Resource Group name [string] $rg = Read-Host "Please enter a new resource group name [rg##] " } #end Do Until (($rg) -match '[RG]{2}[0-9][0-9]') Do { # This is a uniquely assigned number for each course attendee so that the domain and Azure resources will also have unique names within the same course # For class-wide demo scripts, this number will be the last 4 digits of the request number [string]$AttendeeNum = Read-Host "Please enter your 4 digit attendee or request number, i.e. [0000] " } Until ($AttendeeNum -match '[0-9][0-9][0-9][0-9]' -AND $AttendeeNum.Length -eq 4) Do { # The site code refers to a 3 letter airport code of the nearest major airport to the training site [string]$SiteCode = Read-Host "Please enter your 3 character site code, i.e. [ATL] " $SiteCode = $SiteCode.ToUpper() } #end Do Until ($SiteCode -match '[A-Z]{3}' -AND $SiteCode.Length -eq 3) Do { # The site code refers to a 3 letter airport code of the nearest major airport to the training site [int]$InstanceCount = Read-Host "Please enter the total number of DC instances required [1-4] " } #end Do Until ($InstanceCount -le 4 -AND $InstanceCount -ne $null) # Create and populate prompts object with property-value pairs # PROMPTS (PromptsObj) $PromptsObj = [PSCustomObject]@{ pVerifySummary = "Is this information correct? [YES/NO]" pAskToOpenLog = "Would you like to open the deployment log now ? [YES/NO]" } #end $PromptsObj # Create and populate responses object with property-value pairs # RESPONSES (ResponsesObj): Initialize all response variables with null value $ResponsesObj = [PSCustomObject]@{ pProceed = $null pOpenLogNow = $null } #end $ResponsesObj # Construct custom path for log file $LogPath = $env:HOMEPATH + "\" + $SiteCode + "-" + $AttendeeNum If (!(Test-Path $LogPath)) { New-Item -Path $LogPath -ItemType Directory } #End If # Create log file with a "u" formatted time-date stamp $StartTime = (((get-date -format u).Substring(0,16)).Replace(" ", "-")).Replace(":","") $24hrTime = $StartTime.Substring(11,4) $LogFile = "New-AzureRmVmAS" + "-" + $StartTime + ".log" $Log = Join-Path -Path $LogPath -ChildPath $LogFile New-Item -Path $Log -ItemType File -Verbose # Region is specified directly in script $Region = "East US 2" New-AzureRmResourceGroup -Name $rg -Location $Region -Verbose # Storage account name prefix $SaPrefix = "sto" # Generate storage account name based on prefix and attendee number $StorageAcctName = $SaPrefix + $AttendeeNum # VM image details $Publisher = "MicrosoftWindowsServer" $offer = "WindowsServer" [string]$sku = "Windows-Server-Technical-Preview" $ImageName2016TP = Get-AzureRmVMImage –Location $Region –Offer $offer –PublisherName $publisher –SKUs $sku $Version = "latest" # User name is specified directly in script $UniversalAdmName = "ent.g001.s001" # Virtual Machine size $VmSize = "Standard_A1" # Availability set $AvSetDcName = "AvSetDC" # NTDS volume drive $NtdsDiskName = "NTDS" # SYSVOL volume drive $SysvDiskName = "SYSV" # This is the generic top-level domain that will be used in the FQDN of a new domain that can be created later if desired $gtld = ".lab" $SiteNamePrefix = "net" $cred = Get-Credential -UserName $UniversalAdmName -Message "Enter password for user: $UniversalAdmName :" # $UniversalPW = $cred.GetNetworkCredential().password $DelimDouble = ("=" * 100 ) $Header = "AZURE RM DC DEPLOYMENT DEMO: " + $StartTime # Create and populate site, subnet and VM properties of the FR01 domain with property-value pairs $ObjDomain = [PSCustomObject]@{ pFQDN = "R" + $AttendeeNum + $gtld pDomainName = "R" + $AttendeeNum pSite = $SiteNamePrefix + $AttendeeNum # Subnet names matches the VM roles (DC = Domain Controller, AP = Application servers or member servers) pSubNetDC = "DC" pSubNetAP = "AP" pDC = $SiteCode + "DC" # Based on the latest image of Windows Server 2016 } #end $ObjDomain # Subnet for domain controllers $DcSubnet = New-AzureRmVirtualNetworkSubnetConfig -Name $ObjDomain.pSubnetDC -AddressPrefix 10.0.0.0/28 -Verbose # Subnet for member servers (AP = Application servers) $ApSubnet = New-AzureRmVirtualNetworkSubnetConfig -Name $ObjDomain.pSubnetAP -AddressPrefix 10.0.0.16/28 -Verbose $Vnet = New-AzureRmVirtualNetwork -Name $ObjDomain.pSite -ResourceGroupName $rg -Location $Region -AddressPrefix 10.0.0.0/26 -Subnet $DcSubnet,$ApSubnet -Verbose # NSG Configuration # https://www.petri.com/create-azure-network-security-group-using-arm-powershell # Create the NSG names using 'NSG-' as a prefix $NsgDcSubnetName = "NSG-$($ObjDomain.pSubnetDC)" $NsgApSubnetName = "NSG-$($ObjDomain.pSubnetAP)" # Create the AllowRdpInbound rules $NsgRuleAllowRdpIn = New-AzureRmNetworkSecurityRuleConfig -Name "AllowRdpInbound" -Direction Inbound -Priority 100 -Access Allow -SourceAddressPrefix "Internet" -SourcePortRange "*" ` -DestinationAddressPrefix "VirtualNetwork" -DestinationPortRange 3389 -Protocol Tcp -Verbose $NsgDcSubnetObj = New-AzureRmNetworkSecurityGroup -Name $NsgDcSubnetName -ResourceGroupName $rg -Location $Region -SecurityRules $NsgRuleAllowRdpIn -Verbose $NsgApSubnetObj = New-AzureRmNetworkSecurityGroup -Name $NsgApSubnetName -ResourceGroupName $rg -Location $Region -SecurityRules $NsgRuleAllowRdpIn -Verbose Set-AzureRmVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $ObjDomain.pSubnetDC -AddressPrefix $DcSubnet.AddressPrefix -NetworkSecurityGroup $NsgDcSubnetObj | Set-AzureRmVirtualNetwork -Verbose Set-AzureRmVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $ObjDomain.pSubnetAP -AddressPrefix $ApSubnet.AddressPrefix -NetworkSecurityGroup $NsgApSubnetObj | Set-AzureRmVirtualNetwork -Verbose # Specify disk size as 10 GiB [int]$DataDiskSize = 10 # Create the avialability set for the [future] DCs $DcAvSet = New-AzureRmAvailabilitySet -ResourceGroupName $rg -Name $AvSetDcName -Location $Region -Verbose # Populate Summary Display Object # Add properties and values # Make all values upper-case $SummObj = [PSCustomObject]@{ SUBSCRIPTION = $Subscription.ToUpper() RESOURCEGROUP = $rg SITECODE = $SiteCode.ToUpper() ATTENDEENUM = $AttendeeNum.ToUpper() DOMAINFQDN = $ObjDomain.pFQDN.ToUpper() DOMAINNETBIOS = $ObjDomain.pDomainName.ToUpper() SITENAME = $ObjDomain.pSite.ToUpper() DCSUBNET = $ObjDomain.pSubNetDC.ToUpper() NSGDC = $NsgDcSubnetName.ToUpper() APSUBNET = $ObjDomain.pSubNetAP.ToUpper() NSGAP = $NsgApSubnetName.ToUpper() DCPREFIX = $ObjDomain.pDC.ToUpper() # This is the number of VMs and associated VM resources that will be created INSTANCES = $InstanceCount STORAGEACCT = $StorageAcctName.ToUpper() REGION = $Region.ToUpper() NETCONFIGDIR = $NetConfigDir LOGPATH = $Log } #end $SummObj #endregion INITIALIZE VALUES #region FUNCTIONS # Create DC VM Function Add-VM { # If the number of servers will be less than 9, pad with 0, so that the 3rd server would have a pulbic ip of dcvip03 instead of dcvip3 or a nic of dcnic03 as opposed to dcnic3. # This keeps the alignment consistent where all resources will have the same name lengths Write-WithTime -Output "Padding public IP and NIC resource names if necessary..." -Log $Log Switch ($i) { { $i -le 9 } { $DcVipPrefix = "dcvip0" $DcNicPrefix = "dcnic0" } #end condition default { $DcVipPrefix = "dcvip" $DcNicPrefix = "dcnic" } #end default } #end Switch # Create the public ip (VIP) and NIC names based on the prefix and index Write-WithTime -Output "Creating public IP name..." -Log $Log $DcVipName = $DcVipPrefix + $i Write-WithTime -Output "Creating NIC name..." -Log $Log $DcNicName = $DcNicPrefix + $i # Construct the drive names for the SYSTEM, NTDS and SYSVOL drives Write-WithTime -Output "Constructing SYSTEM drive name page blob..." -Log $Log $DCSYSTvhdUri = $sa.PrimaryEndpoints.Blob.ToString() + "vhds/" + "$($ObjDomain.pDC)-SYST.vhd" Write-WithTime -Output "Constructing NTDS drive name page blob..." -Log $Log $DCNTDSvhdUri = $sa.PrimaryEndpoints.Blob.ToString() + "vhds/" + "$($ObjDomain.pDC)-NTDS.vhd" Write-WithTime -Output "Constructing SYSVOL drive name page blob..." -Log $Log $DCSYSVvhdUri = $sa.PrimaryEndpoints.Blob.ToString() + "vhds/" + "$($ObjDomain.pDC)-SYSV.vhd" # $x represents the value of the last octect of the private IP address. We skip the first 3 addresses in the network address because they are always reserved in Azure $x = $i + 3 # NOTE: Domain labels have to be lower case Write-WithTime -Output "Creating DNS domain label..." -Log $Log $DomainLabel = $objDomain.pDC.ToLower() + "-pip" Write-WithTime -Output "Creating public IP..." -Log $Log # Now we can string all the pre-requisites together to construct both the VIP and NIC $DCvip = New-AzureRmPublicIpAddress -ResourceGroupName $rg -Name $DcVipName -Location $Region -AllocationMethod Static -DomainNameLabel $DomainLabel -Verbose Write-WithTime -Output "Creating NIC..." -Log $Log $DCnic = New-AzureRmNetworkInterface -ResourceGroupName $rg -Name $DcNicName -Location $Region -PrivateIpAddress "10.0.0.$x" -SubnetId $Vnet.Subnets[0].Id -PublicIpAddressId $DCvip.Id -Verbose # If the VM doesn't aready exist, configure and create it If (!((Get-AzureRmVM -ResourceGroupName $rg).Name -match $ObjDomain.pDC)) { Write-WithTime -Output "VM $($ObjDomain.pDC) doesn't already exist. Configuring..." -Log $Log # Setup new vm configuration $DcvmConfig = New-AzureRmVMConfig –VMName $ObjDomain.pDC -VMSize $vmSize -AvailabilitySetId $DcAvSet.Id | Set-AzureRmVMOperatingSystem -Windows -ComputerName $ObjDomain.pDC -Credential $cred -ProvisionVMAgent -EnableAutoUpdate | Set-AzureRmVMSourceImage -PublisherName $publisher -Offer $offer -Skus $sku -Version $version | Set-AzureRmVMOSDisk -Name $ObjDomain.pDC -VhdUri $DCSYSTvhdUri -Caching ReadWrite -CreateOption fromImage | Add-AzureRmVMNetworkInterface -Id $DCnic.Id -Verbose # Create new VM Write-WithTime -Output "Creating VM from configuration..." -Log $Log New-AzureRmVM -ResourceGroupName $rg -Location $Region -VM $DcvmConfig -Verbose # Add NIC Write-WithTime -Output "Adding NIC..." -Log $Log Set-AzureRmNetworkInterface -NetworkInterface $DCnic -Verbose # Add data disks Write-WithTime -Output "Adding data disks..." -Log $Log $vmdc = Get-AzureRmVM -ResourceGroupName $rg -Name $ObjDomain.pDC Write-WithTime -Output "Adding NTDS disk..." -Log $Log Add-AzureRmVMDataDisk -VM $vmdc -Name $NtdsDiskName -VhdUri $DCNTDSvhdUri -LUN 0 -Caching None -DiskSizeinGB $DataDiskSize -CreateOption Empty -Verbose Write-WithTime -Output "Adding SYSVOL disk..." -Log $Log Add-AzureRmVMDataDisk -VM $vmdc -Name $SysvDiskName -VhdUri $DCSYSVvhdUri -LUN 1 -Caching None -DiskSizeinGB $DataDiskSize -CreateOption Empty -Verbose # Update disk configuration Write-WithTime -Output "Applying new disk configurations..." -Log $Log Update-AzureRmVM -ResourceGroupName $rg -VM $vmdc -Verbose } #end If else { Write-ToConsoleAndLog -Output "$($ObjDomain.pDC) already exists..." -Log $Log } #end else } #End function #endregion FUNCTIONS #region MAIN # Clear screen # Clear-Host # Display header Write-ToConsoleAndLog -Output $DelimDouble -Log $Log Write-ToConsoleAndLog -Output $Header -Log $Log Write-ToConsoleAndLog -Output $DelimDouble -Log $Log # Display Summary Write-ToConsoleAndLog -Output $SummObj -Log $Log Write-ToConsoleAndLog -Output $DelimDouble -Log $Log # Verify parameter values Do { $ResponsesObj.pProceed = read-host $PromptsObj.pVerifySummary $ResponsesObj.pProceed = $ResponsesObj.pProceed.ToUpper() } Until ($ResponsesObj.pProceed -eq "Y" -OR $ResponsesObj.pProceed -eq "YES" -OR $ResponsesObj.pProceed -eq "N" -OR $ResponsesObj.pProceed -eq "NO") # Record prompt and response in log Write-ToLogOnly -Output $PromptsObj.pVerifySummary -Log $Log Write-ToLogOnly -Output $ResponsesObj.pProceed -Log $Log # Exit if user does not want to continue if ($ResponsesObj.pProceed -eq "N" -OR $ResponsesObj.pProceed -eq "NO") { Write-ToConsoleAndLog -Output "Deployment terminated by user..." -Log $Log PAUSE EXIT } #end if ne Y else { # Proceed with deployment Write-ToConsoleAndLog -Output "Deploying environment..." -Log $Log # TASK-ITEM: 0005 Comment 2 lines below before production run, uncomment for testing/debugging only # pause # exit # Storage Write-WithTime -Output "Creating storage account $StorageAcctName ..." -Log $Log # The following error will be displayed, due to the ARM PowerShell module missing the Test-Azure command. See: Symptom: https://github.com/Azure/azure-powershell/issues/639 # Test-AzureName : No default subscription has been designated. Use Select-AzureSubscription -Default <subscriptionName> to set the default subscription... If (!(Get-AzureRmStorageAccount -ResourceGroupName $rg -Name $StorageAcctName -ErrorAction SilentlyContinue)) { Write-WithTime -Output "Storage account $StorageAcctName does not already exist. Creating..." -Log $Log New-AzureRmStorageAccount -ResourceGroupName $rg -Name $StorageAcctName -Location $Region -Type Standard_LRS -Verbose } #end If else { Write-WithTime -Output "Storage Account: $StorageAcctName already exist. Skipping..." -Log $Log } #end else $sa = Get-AzureRmStorageAccount -ResourceGroupName $rg -Name $StorageAcctName # Create DC VM(s). Note that we pad the VM name here again, as we did for the VIPs and NICs above to ensure a consistent name length for VM resources Write-WithTime -Output "Padding name of VM for a consistent length if necessary..." -Log $Log For ($i = 1;$i -le $InstanceCount;$i++) { Switch ($i) { { $i -le 9 } { $ObjDomain.pDC = $SiteCode + "DC0" + $i } default { # The VM name is constructed from the site code, "DC" role prefix and the numeric index $i $ObjDomain.pDC = $SiteCode + "DC" + $i } #end default } #end switch Write-WithTime -Output "Building $($ObjDomain.pDC)..." -Log $Log Add-VM } #end For ($i...) } #end else #endregion MAIN #region FOOTER # Calculate elapsed time Write-WithTime -Output "Calculating script execution time..." -Log $Log Write-WithTime -Output "Getting current date/time..." -Log $Log $StopTimer = Get-Date Write-WithTime -Output "Formating date/time to replace commas(,) with dashes(-)..." -Log $Log $EndTime = (((Get-Date -format u).Substring(0,16)).Replace(" ", "-")).Replace(":","") Write-WithTime -Output "Calculating elapsed time..." -Log $Log $ExecutionTime = New-TimeSpan -Start $BeginTimer -End $StopTimer $Footer = "SCRIPT COMPLETED AT: " Write-ToConsoleAndLog -Output $DelimDouble -Log $Log Write-ToConsoleAndLog -Output "$Footer + $EndTime" -Log $Log Write-ToConsoleAndLog -Output "TOTAL SCRIPT EXECUTION TIME: $ExecutionTime" -Log $Log Write-ToConsoleAndLog -Output $DelimDouble -Log $Log # Prompt to open log Do { $ResponsesObj.pOpenLogNow = read-host $PromptsObj.pAskToOpenLog $ResponsesObj.pOpenLogNow = $ResponsesObj.pOpenLogNow.ToUpper() } Until ($ResponsesObj.pOpenLogNow -eq "Y" -OR $ResponsesObj.pOpenLogNow -eq "YES" -OR $ResponsesObj.pOpenLogNow -eq "N" -OR $ResponsesObj.pOpenLogNow -eq "NO") # Exit if user does not want to continue if ($ResponsesObj.pOpenLogNow -eq "Y" -OR $ResponsesObj.pOpenLogNow -eq "YES") { Start-Process notepad.exe $Log } #end if # End of script Write-WithTime -Output "END OF SCRIPT!" -Log $Log #endregion FOOTER Pause EXIT |