omat/omat.ps1 (1,608 lines of code) (raw):
[CmdletBinding()]
Param(
[Parameter()]
[Alias("h")]
[switch]$Help,
[Parameter(mandatory=$false)]
[string]$SourceFolder=".\",
[Parameter(mandatory=$false)]
[string]$OutputFile="",
[Parameter(mandatory=$false)]
[string]$AzureRegion="westus",
[Parameter(mandatory=$false)]
[string]$TemplateFileName="template.xlsm",
[Parameter()]
[switch]$NoAwr
)
######################################### Utility Functions #########################################
function FirstLineOffset {
param (
$htmlTable
)
$headerOffset=0
:rowLoop for($i=0;$i -lt $htmlTable.rows.length;$i++)
{
$headerTagFound=$false
:cellLoop for($j=0;$j -lt $htmlTable.rows[$i].cells.length;$j++)
{
if($htmlTable.rows[$i].cells[$j].tagName -ieq "TH")
{
$headerTagFound=$true
break cellLoop
}
}
if ($headerTagFound -eq $false)
{
$headerOffset=$i
break rowLoop
}
}
return $headerOffset
}
function ConvertNumberOrDefault {
param (
[string]$textToConvert,
[decimal]$defaultValue
)
[decimal]$parsedNumber=$textToConvert -as [Decimal]
if($null -ne $parsedNumber)
{
return $parsedNumber
}
else {
return $defaultValue
}
}
function ResetTable([object]$listObject){
while ($listObject.ListRows.Count -gt 0)
{
$listObject.ListRows.Item(1).Range.EntireRow.Delete() | Out-Null
}
}
function AppendRow([object]$listObject){
if ($null -eq $listObject.InsertRowRange)
{
$listObject.ListRows.Item($listObject.ListRows.Count).Range.Offset(1).EntireRow.Insert() | Out-Null
}
else {
$listObject.InsertRowRange.Cells(1) = " "
}
}
function ParseSkuSizeString([string]$size){
$allmatches=$global:azureSizeStringRegex.Matches($size)
$objectProps=[ordered]@{
Size = $size
Family = $allmatches.Groups[1].Value
Subfamily = $allmatches.Groups[2].Value
vCpus = $allmatches.Groups[3].Value
ConstrainedvCpus = $allmatches.Groups[4].Value
Capabilities = $allmatches.Groups[5].Value.ToLower()
AMDProcessor = $allmatches.Groups[5].Value.ToLower().Contains('a')
BlockStoragePerformance = $allmatches.Groups[5].Value.ToLower().Contains('b')
Diskful = $allmatches.Groups[5].Value.ToLower().Contains('d')
IsolatedSize = $allmatches.Groups[5].Value.ToLower().Contains('i')
LowMemory = $allmatches.Groups[5].Value.ToLower().Contains('l')
MemoryIntensive = $allmatches.Groups[5].Value.ToLower().Contains('m')
ARMProcessor = $allmatches.Groups[5].Value.ToLower().Contains('p')
TinyMemory = $allmatches.Groups[5].Value.ToLower().Contains('t')
PremiumStorage = $allmatches.Groups[5].Value.ToLower().Contains('s')
AcceleratorType = $allmatches.Groups[6].Value
Version = $allmatches.Groups[7].Value
Promo = $allmatches.Groups[8].Value
}
$obj = New-Object -TypeName PSCustomObject -Property $objectProps
if($obj.ConstrainedvCpus.StartsWith("-"))
{
$obj.ConstrainedvCpus=$obj.ConstrainedvCpus.Substring(1)
}
if($obj.Version.StartsWith("_"))
{
$obj.Version=$obj.Version.Substring(1)
}
return $obj
}
function ParseAWR_Normal([object]$html, [string]$awrReportFileName, [bool]$isMultiTenantDb){
#first find the release
$firstTable=$html.body.getElementsByTagName('table')[0]
$releaseNumber=""
if($firstTable)
{
for($i=0;$i -lt $firstTable.rows[0].cells.Length;$i++)
{
if($firstTable.rows[0].cells[$i].InnerText -ieq "Release")
{
$releaseNumber=$firstTable.rows[1].cells[$i].InnerText
}
}
}
if([string]::IsNullOrEmpty($releaseNumber))
{
throw "Release number cannot be found while processing file `"$awrReportFileName`""
}
$objectProps=[ordered]@{
InstanceIndex=0
Release =$releaseNumber
DBName =""
InstanceName =""
HostName =""
ElapsedTime =0
DBTime =0
DBCPU =0
CPUs =0
Cores =0
Memory =0
BusyCPU =0
SGAUse =0
PGAUse =0
ReadThroughput =0
WriteThroughput =0
ReadIOPS =0
WriteIOPS =0
TotalThroughput =0
TotalIOPS =0
CPUTotalCapacity=0
ORAUse =0
SourceCPUHTFactor=0
AverageActiveSessions=0
AWRReportFileName=$awrReportFileName
ReportType="Normal"
}
$awrObj=New-Object -TypeName PSCustomObject -Property $objectProps #this is to provide ordered list of object properties
$versionArray = $releaseNumber.Split(".")
$releaseVersion = [Version]::new( (&{If([String]::IsNullOrEmpty($versionArray[0])) {-1} Else {$versionArray[0]}}),
(&{If([String]::IsNullOrEmpty($versionArray[1])) {-1} Else {$versionArray[1]}}),
(&{If([String]::IsNullOrEmpty($versionArray[2])) {-1} Else {$versionArray[2]}}),
(&{If([String]::IsNullOrEmpty($versionArray[3])) {-1} Else {$versionArray[3]}})
)
$releaseVersion_11 = [Version]::new(11,0)
$releaseVersion_11_2_0_4 = [Version]::new(11,2,0,4)
$releaseVersion_13 = [Version]::new(13,0)
if($releaseVersion -lt $releaseVersion_11_2_0_4)
{
$tables=$html.body.getElementsByTagName('table')
$awrObj.InstanceIndex=$tables[0].rows[1].cells[3].InnerText
$awrObj.DBName =$tables[0].rows[1].cells[0].InnerText
$awrObj.InstanceName =$tables[0].rows[1].cells[2].InnerText
if($releaseVersion -lt $releaseVersion_11)
{$awrObj.HostName =$tables[0].rows[1].cells[6].InnerText}
else
{$awrObj.HostName =$tables[1].rows[1].cells[0].InnerText}
if($releaseVersion -lt $releaseVersion_11)
{$snapshotTableOffset=1}
else
{$snapshotTableOffset=2}
$tblSnapshot = $tables[$snapshotTableOffset]
if ($tblSnapshot)
{
$offset=FirstLineOffset($tblSnapshot)
for($i=0;$i -lt $tblSnapshot.rows.Length-1;$i++)
{
if($tblSnapshot.rows[$i+$offset].cells[0].InnerText -like "Elapsed*")
{
$awrObj.ElapsedTime= ConvertNumberOrDefault ($tblSnapshot.rows[$i+$offset].cells[2].InnerText -replace "[^\d*\,?\.?\d*$/]",'') 0
}
elseif($tblSnapshot.rows[$i+$offset].cells[0].InnerText -like "DB Time*")
{
$awrObj.DBTime= ConvertNumberOrDefault ($tblSnapshot.rows[$i+$offset].cells[2].InnerText -replace "[^\d*\,?\.?\d*$/]",'') 0
}
}
}
else {
Write-Host "HTML table cannot be found (First table) while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblTimeModel=$html.body.getElementsByTagName('table') | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
$cells[0].innerText -eq "Statistic Name" -and
$cells[1].innerText -eq "Time (s)" -and
$cells[2].innerText -eq "% of DB Time" -and
$cells.Length -eq 3
}
if ($tblTimeModel)
{
$offset=FirstLineOffset($tblTimeModel)
for($i=0;$i -lt $tblTimeModel.rows.Length-1;$i++)
{
if($tblTimeModel.rows[$i+$offset].cells[0].InnerText -ieq "DB CPU")
{
$awrObj.DBCPU=ConvertNumberOrDefault $tblTimeModel.rows[$i+$offset].cells[1].InnerText 0
}
}
}
else {
Write-Host "HTML table cannot be found (Headers: Statistic Name,Time (s),% of DB Time) while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
if($releaseVersion -lt $releaseVersion_11)
{
$tblOSStats=$html.body.getElementsByTagName('table') | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
$cells[0].innerText -eq "Statistic" -and
$cells[1].innerText -eq "Total" -and
$cells.Length -eq 2
}
}
else {
$tblOSStats=$html.body.getElementsByTagName('table') | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
$cells[0].innerText -eq "Statistic" -and
$cells[1].innerText -eq "Value" -and
$cells[2].innerText -eq "End Value" -and
$cells.Length -eq 3
}
}
if ($tblOSStats)
{
$offset=FirstLineOffset($tblOSStats)
$busyTime=0
$idleTime=0
$userTime=0
for($i=0;$i -lt $tblOSStats.rows.Length-1;$i++)
{
if($tblOSStats.rows[$i+$offset].cells[0].InnerText -like "PHYSICAL_MEMORY_BYTES")
{
$awrObj.Memory= ConvertNumberOrDefault $tblOSStats.rows[$i+$offset].cells[1].InnerText 0
$awrObj.Memory/=1024*1024*1024
}
elseif($tblOSStats.rows[$i+$offset].cells[0].InnerText -like "NUM_CPU_CORES")
{
$awrObj.Cores= ConvertNumberOrDefault $tblOSStats.rows[$i+$offset].cells[1].InnerText 0
}
elseif($tblOSStats.rows[$i+$offset].cells[0].InnerText -like "NUM_CPUS")
{
$awrObj.CPUs= ConvertNumberOrDefault $tblOSStats.rows[$i+$offset].cells[1].InnerText 0
}
elseif($tblOSStats.rows[$i+$offset].cells[0].InnerText -like "BUSY_TIME")
{
$busyTime= ConvertNumberOrDefault $tblOSStats.rows[$i+$offset].cells[1].InnerText 0
}
elseif($tblOSStats.rows[$i+$offset].cells[0].InnerText -like "IDLE_TIME")
{
$idleTime= ConvertNumberOrDefault $tblOSStats.rows[$i+$offset].cells[1].InnerText 0
}
elseif($tblOSStats.rows[$i+$offset].cells[0].InnerText -like "USER_TIME")
{
$userTime= ConvertNumberOrDefault $tblOSStats.rows[$i+$offset].cells[1].InnerText 0
}
if($busyTime -gt 0 -and $idleTime -gt 0 -and $userTime -gt 0)
{
$awrObj.BusyCPU=$userTime/($busyTime+$idleTime)
}
}
if($awrObj.Cores -le 0)
{
$awrObj.Cores=$awrObj.CPUs
}
}
else{
Write-Host "HTML table cannot be found (Headers: Statistic,Total) while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
if($releaseVersion -lt $releaseVersion_11)
{
$tblInitOraParameters=$html.body.getElementsByTagName('table') | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
$cells[0].innerText -eq "Parameter Name" -and
$cells[1].innerText -eq "Begin value" -and
$cells[2].innerText -eq "End value (if different)" -and
$cells.Length -eq 3
}
if ($tblInitOraParameters)
{
$offset=FirstLineOffset($tblInitOraParameters)
for($i=0;$i -lt $tblInitOraParameters.rows.Length-1;$i++)
{
if($tblInitOraParameters.rows[$i+$offset].cells[0].InnerText -like "pga_aggregate_target")
{
$awrObj.PGAUse= ConvertNumberOrDefault $tblInitOraParameters.rows[$i+$offset].cells[1].InnerText 0
$awrObj.PGAUse/=1024*1024
}
elseif($tblInitOraParameters.rows[$i+$offset].cells[0].InnerText -like "sga_target")
{
$awrObj.SGAuse= ConvertNumberOrDefault $tblInitOraParameters.rows[$i+$offset].cells[1].InnerText 0
$awrObj.SGAuse/=1024*1024
}
}
}
else{
Write-Host "HTML table cannot be found (Headers: Parameter Name,Begin value,End value (if different)) while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
else {
$tblMemoryStatistics=$html.body.getElementsByTagName('table') | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
[String]::IsNullOrEmpty($cells[0].innerText) -and
$cells[1].innerText -eq "Begin" -and
$cells[2].innerText -eq "End" -and
$_.tBodies[0].rows.Length -gt 1 -and
$_.tBodies[0].rows[1].cells[0].InnerText -like 'Host Mem*' -and
$cells.Length -eq 3
}
if ($tblMemoryStatistics)
{
$offset=FirstLineOffset($tblMemoryStatistics)
for($i=0;$i -lt $tblMemoryStatistics.rows.Length-1;$i++)
{
if($tblMemoryStatistics.rows[$i+$offset].cells[0].InnerText -like "PGA use*")
{
$awrObj.PGAUse= ConvertNumberOrDefault $tblMemoryStatistics.rows[$i+$offset].cells[2].InnerText 0
if($awrObj.PGAUse -le 0)
{$awrObj.PGAUse= ConvertNumberOrDefault $tblMemoryStatistics.rows[$i+$offset].cells[1].InnerText 0}
$awrObj.PGAUse/=1024
}
elseif($tblMemoryStatistics.rows[$i+$offset].cells[0].InnerText -like "SGA use*")
{
$awrObj.SGAuse= ConvertNumberOrDefault $tblMemoryStatistics.rows[$i+$offset].cells[2].InnerText 0
if($awrObj.SGAUse -le 0)
{$awrObj.SGAuse= ConvertNumberOrDefault $tblMemoryStatistics.rows[$i+$offset].cells[1].InnerText 0}
$awrObj.SGAuse/=1024
}
}
}
else{
Write-Host "HTML table cannot be found (Headers: <empty>,Begin,End / Row[1].Cells[0]: 'Host Mem') while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
$tblInstanceActivityStats=$html.body.getElementsByTagName('table') | Where-Object {
$cells = $_.tBodies[0].rows[0].cells
$cells[0].innerText -eq "Statistic" -and
$cells[1].innerText -eq "Total" -and
$cells[2].innerText -eq "per Second" -and
$cells[3].innerText -eq "per Trans" -and
$cells.Length -eq 4
}
if ($tblInstanceActivityStats)
{
$offset=FirstLineOffset($tblInstanceActivityStats)
for($i=0;$i -lt $tblInstanceActivityStats.rows.Length-1;$i++)
{
if($tblInstanceActivityStats.rows[$i+$offset].cells[0].InnerText -like "physical read bytes")
{
$awrObj.ReadThroughput= ConvertNumberOrDefault $tblInstanceActivityStats.rows[$i+$offset].cells[2].InnerText 0
$awrObj.ReadThroughput/=1024*1024
}
elseif($tblInstanceActivityStats.rows[$i+$offset].cells[0].InnerText -like "physical read IO requests")
{
$awrObj.ReadIOPS= ConvertNumberOrDefault $tblInstanceActivityStats.rows[$i+$offset].cells[2].InnerText 0
}
elseif($tblInstanceActivityStats.rows[$i+$offset].cells[0].InnerText -like "physical write bytes")
{
$awrObj.WriteThroughput= ConvertNumberOrDefault $tblInstanceActivityStats.rows[$i+$offset].cells[2].InnerText 0
$awrObj.WriteThroughput/=1024*1024
}
elseif($tblInstanceActivityStats.rows[$i+$offset].cells[0].InnerText -like "physical write IO requests")
{
$awrObj.WriteIOPS= ConvertNumberOrDefault $tblInstanceActivityStats.rows[$i+$offset].cells[2].InnerText 0
}
}
}
else{
Write-Host "HTML table cannot be found (Headers: Statistic,Total,per Second,per Trans) while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
elseif($releaseVersion -ge $releaseVersion_11_2_0_4)
{
$tblDBInstance=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*database instance information'}
if ($tblDBInstance)
{
if($releaseVersion -lt $releaseVersion_13)
{
$awrObj.DBName =$tblDBInstance.rows[1].cells[0].InnerText
$awrObj.InstanceName =$tblDBInstance.rows[1].cells[2].InnerText
$awrObj.InstanceIndex=$tblDBInstance.rows[1].cells[3].InnerText
}
else
{
$awrObj.DBName =$tblDBInstance[0].rows[1].cells[0].InnerText
$awrObj.InstanceName =$tblDBInstance[1].rows[1].cells[0].InnerText
$awrObj.InstanceIndex=$tblDBInstance[1].rows[1].cells[1].InnerText
}
}
else {
Write-Host "HTML table cannot be found summary=`"*database instance information`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblHostInformation=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*host information'}
if ($tblHostInformation)
{
$offset=FirstLineOffset($tblHostInformation)
$awrObj.HostName= $tblHostInformation.rows[$offset].cells[0].InnerText
$awrObj.CPUs = ConvertNumberOrDefault $tblHostInformation.rows[$offset].cells[2].InnerText
$awrObj.Cores = ConvertNumberOrDefault $tblHostInformation.rows[$offset].cells[3].InnerText
$awrObj.Memory = ConvertNumberOrDefault $tblHostInformation.rows[$offset].cells[5].InnerText
}
else {
Write-Host "HTML table cannot be found summary=`"*host information`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblSnapshot=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*snapshot information'}
if ($tblSnapshot)
{
$offset=FirstLineOffset($tblSnapshot)
for($i=0;$i -lt $tblSnapshot.rows.Length-1;$i++)
{
if($tblSnapshot.rows[$i+$offset].cells[0].InnerText -like "Elapsed*")
{
$awrObj.ElapsedTime= ConvertNumberOrDefault ($tblSnapshot.rows[$i+$offset].cells[2].InnerText -replace "[^\d*\,?\.?\d*$/]",'') 0
}
elseif($tblSnapshot.rows[$i+$offset].cells[0].InnerText -like "DB Time*")
{
$awrObj.DBTime= ConvertNumberOrDefault ($tblSnapshot.rows[$i+$offset].cells[2].InnerText -replace "[^\d*\,?\.?\d*$/]",'') 0
}
}
}
else {
Write-Host "HTML table cannot be found summary=`"*snapshot information`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblTimeModel=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*time model statistics*'}
if ($tblTimeModel)
{
$offset=FirstLineOffset($tblTimeModel)
for($i=0;$i -lt $tblTimeModel.rows.Length-1;$i++)
{
if($tblTimeModel.rows[$i+$offset].cells[0].InnerText -ieq "DB CPU")
{
$awrObj.DBCPU=ConvertNumberOrDefault $tblTimeModel.rows[$i+$offset].cells[1].InnerText 0
}
}
}
else {
Write-Host "HTML table cannot be found summary=`"*time model statistics*`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
if(-not $isMultiTenantDb)
{
$tblHostCPU=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*CPU usage and wait statistics'}
if ($tblHostCPU)
{
$offset=FirstLineOffset($tblHostCPU)
$awrObj.BusyCPU=ConvertNumberOrDefault $tblHostCPU.rows[$offset].cells[1].InnerText 0
$awrObj.BusyCPU/=100
}
else {
Write-Host "HTML table cannot be found summary=`"*CPU usage and wait statistics`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
else {
$awrObj.BusyCPU=((($awrObj.CPUs * $awrObj.ElapsedTime * 60)-$awrObj.DBCPU)/$awrObj.DBCPU)/100
}
if(-not $isMultiTenantDb)
{
$tblMemoryStats=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*memory statistics'}
if ($tblMemoryStats)
{
$offset=FirstLineOffset($tblMemoryStats)
for($i=0;$i -lt $tblMemoryStats.rows.Length-1;$i++)
{
if($tblMemoryStats.rows[$i+$offset].cells[0].InnerText -like "SGA use (MB)*")
{
$begin=ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[1].InnerText 0
$end =ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[2].InnerText 0
$awrObj.SGAUse= [double]([Math]::Max($begin, $end))
}
elseif($tblMemoryStats.rows[$i+$offset].cells[0].InnerText -like "PGA use (MB)*")
{
$begin=ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[1].InnerText 0
$end =ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[2].InnerText 0
$awrObj.PGAUse= [double]([Math]::Max($begin, $end))
}
}
}
else {
Write-Host "HTML table cannot be found summary=`"*memory statistics`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
else {
$tblMemoryStats=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*database resource limits'}
if ($tblMemoryStats)
{
$offset=FirstLineOffset($tblMemoryStats)
for($i=0;$i -lt $tblMemoryStats.rows.Length-1;$i++)
{
if($tblMemoryStats.rows[$i+$offset].cells[0].InnerText -like "SGA Target*")
{
$begin=ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[1].InnerText 0
$end =ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[2].InnerText 0
$awrObj.SGAUse= [double]([Math]::Max($begin, $end))
$awrObj.SGAUse=$awrObj.SGAUse/1024/1024
}
elseif($tblMemoryStats.rows[$i+$offset].cells[0].InnerText -like "PGA Target*")
{
$begin=ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[1].InnerText 0
$end =ConvertNumberOrDefault $tblMemoryStats.rows[$i+$offset].cells[2].InnerText 0
$awrObj.PGAUse= [double]([Math]::Max($begin, $end))
$awrObj.PGAUse=$awrObj.PGAUse/1024/1024
}
}
}
else {
Write-Host "HTML table cannot be found summary=`"*memory statistics`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
$tblIOStats=$html.body.getElementsByTagName('table') | Where {$_.summary -like '*IO Statistics for different file types*'}
if ($tblIOStats)
{
$awrObj.ReadIOPS = ConvertAbbreviatedNumberOrDefault $tblIOStats.rows[$tblIOStats.rows.Length-1].cells[2].InnerText 0 1024
$awrObj.ReadThroughput = ConvertAbbreviatedNumberOrDefault $tblIOStats.rows[$tblIOStats.rows.Length-1].cells[3].InnerText 0 1024
$awrObj.ReadThroughput/=1024*1024
$awrObj.WriteIOPS = ConvertAbbreviatedNumberOrDefault $tblIOStats.rows[$tblIOStats.rows.Length-1].cells[5].InnerText 0 1024
$awrObj.WriteThroughput= ConvertAbbreviatedNumberOrDefault $tblIOStats.rows[$tblIOStats.rows.Length-1].cells[6].InnerText 0 1024
$awrObj.WriteThroughput/=1024*1024
}
else {
Write-Host "HTML table cannot be found summary=`"*IO Statistics for different file types*`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
}
else {
Write-Host "Unsupported relese $releaseNumber found while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
return @($awrObj)
}
function IsNumber {
param ([string]$text)
return [bool]($text -as [double])
}
function ConvertAbbreviatedNumberOrDefault {
param (
[string]$textToConvert,
[decimal]$defaultValue,
[decimal]$baseMultiplier #eg if you use 1000 or 1024 for multiplication
)
[decimal]$parsedNumber=0
[decimal]$multiplier=1
[string]$suffix=$textToConvert.Substring($textToConvert.Length-1,1)
if ($suffix -ieq 'K' ){$multiplier=[Math]::Pow($baseMultiplier,1)}
elseif($suffix -ieq 'M' ){$multiplier=[Math]::Pow($baseMultiplier,2)}
elseif($suffix -ieq 'G' ){$multiplier=[Math]::Pow($baseMultiplier,3)}
elseif($suffix -ieq 'T' ){$multiplier=[Math]::Pow($baseMultiplier,4)}
elseif($suffix -ieq 'P' ){$multiplier=[Math]::Pow($baseMultiplier,5)}
if(-not (IsNumber($suffix))){$textToConvert=$textToConvert.Substring(0,$textToConvert.Length-1)}
$parsedNumber=ConvertNumberOrDefault $textToConvert 0
$parsedNumber*=$multiplier
return $parsedNumber
}
function ParseAWR_RAC([object]$html, [string]$awrReportFileName){
$tblDBSummary=$html.body.getElementsByTagName('table') | Where {$_.summary -like 'Database Summary'}
if ($tblDBSummary)
{
$databaseName=$tblDBSummary.rows[2].cells[1].InnerText
}
else {
throw "HTML table cannot be found summary=`"Database Summary`" while processing file `"$awrReportFileName`""
}
[array]$awrData=$html.body.getElementsByTagName('table') | Where {$_.summary -like 'Database Instances Included In Report*'} | ForEach-Object {$_.rows} | where {$_.cells[0].tagName -ne "TH"} |
ForEach-Object {
$objectProps=[ordered]@{
InstanceIndex=ConvertNumberOrDefault $_.cells[0].InnerText 0
Release =$_.cells[6].InnerText
DBName =$databaseName
InstanceName =$_.cells[1].InnerText
HostName =$_.cells[2].InnerText
ElapsedTime =ConvertNumberOrDefault $_.cells[7].InnerText 0
DBTime =ConvertNumberOrDefault $_.cells[8].InnerText 0
DBCPU =0
CPUs =0
Cores =0
Memory =0
BusyCPU =0
SGAUse =0
PGAUse =0
ReadThroughput =0
WriteThroughput =0
ReadIOPS =0
WriteIOPS =0
TotalThroughput =0
TotalIOPS =0
CPUTotalCapacity=0
ORAUse =0
SourceCPUHTFactor=0
AverageActiveSessions=0
AWRReportFileName=$awrReportFileName
ReportType="RAC"
}
New-Object -TypeName PSCustomObject -Property $objectProps #this is to provide ordered list of object properties
}
if (-not ($awrData))
{
throw "HTML table cannot be found summary=`"Database Instances Included In Report*`" while processing file `"$awrReportFileName`""
}
#If we made it here file is processed at least partially. Try block/finally makes sure whatever is extracted from AWR report is appended into global report
$tblTimeModel=$html.body.getElementsByTagName('table') | Where {$_.summary -like 'Time Model*'}
if ($tblTimeModel)
{
$offset=FirstLineOffset($tblTimeModel)
for($i=0;$i -lt $awrData.Length;$i++)
{
$awrData[$i].DBCPU=ConvertNumberOrDefault $tblTimeModel.rows[$i+$offset].cells[2].InnerText 0
}
}
else {
Write-Host "HTML table cannot be found summary=`"Time Model*`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblOSStatistics=$html.body.getElementsByTagName('table') | Where {$_.summary -like 'OS Statistics By Instance*'}
if ($tblOSStatistics)
{
$offset=FirstLineOffset($tblOSStatistics)
for($i=0;$i -lt $awrData.Length;$i++)
{
$awrData[$i].CPUs =ConvertNumberOrDefault $tblOSStatistics.rows[$i+$offset].cells[ 1].InnerText 0
$awrData[$i].Cores =ConvertNumberOrDefault $tblOSStatistics.rows[$i+$offset].cells[ 2].InnerText 0
$awrData[$i].Memory =ConvertNumberOrDefault $tblOSStatistics.rows[$i+$offset].cells[14].InnerText 0
$awrData[$i].Memory/=1024
$awrData[$i].busyCPU=ConvertNumberOrDefault $tblOSStatistics.rows[$i+$offset].cells[ 6].InnerText 0
$awrData[$i].busyCPU/=100
}
}
else {
Write-Host "HTML table cannot be found summary=`"OS Statistics By Instance*`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblCacheSizes=$html.body.getElementsByTagName('table') | Where {$_.summary -like 'Cache Sizes*'}
if ($tblCacheSizes)
{
$offset=FirstLineOffset($tblCacheSizes)
for($i=0;$i -lt $awrData.Length;$i++)
{
$begin=ConvertNumberOrDefault $tblCacheSizes.rows[$i+$offset].cells[3].InnerText 0
$end =ConvertNumberOrDefault $tblCacheSizes.rows[$i+$offset].cells[4].InnerText 0
$awrData[$i].SGAuse=[Math]::Max($begin, $end)
$begin=ConvertNumberOrDefault $tblCacheSizes.rows[$i+$offset].cells[15].InnerText 0
$end =ConvertNumberOrDefault $tblCacheSizes.rows[$i+$offset].cells[16].InnerText 0
$awrData[$i].PGAuse=[Math]::Max($begin, $end)
}
}
else {
Write-Host "HTML table cannot be found summary=`"Cache Sizes*`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
$tblIOStatByFileType=$html.body.getElementsByTagName('table') | Where {$_.summary -like 'IOStat by File Type*'}
if ($tblIOStatByFileType)
{
$release=$awrData[0].Release
if(($release -like "19*") -or ($release -like "18*"))
{
for($i=0;$i -lt $awrData.Length;$i++)
{
$currentInstance=-1
$rowIndex=-1
for($j=0;$j -lt $tblIOStatByFileType.rows.length; $j++)
{
$currentInstance=ConvertNumberOrDefault $tblIOStatByFileType.rows[$j].cells[0].InnerText -1
if ($currentInstance -eq $awrData[$i].InstanceIndex)
{
$rowIndex=$j
break
}
}
if ($rowIndex -gt -1)
{
$awrData[$i].ReadThroughput =ConvertNumberOrDefault $tblIOStatByFileType.rows[$rowIndex].cells[8].InnerText 0
$awrData[$i].WriteThroughput=ConvertNumberOrDefault $tblIOStatByFileType.rows[$rowIndex].cells[9].InnerText 0
$awrData[$i].ReadIOPS =ConvertNumberOrDefault $tblIOStatByFileType.rows[$rowIndex].cells[4].InnerText 0
$awrData[$i].WriteIOPS =ConvertNumberOrDefault $tblIOStatByFileType.rows[$rowIndex].cells[5].InnerText 0
}
}
}
else
{
$offset=FirstLineOffset($tblIOStatByFileType)
for($i=0;$i -lt $awrData.Length;$i++)
{
$awrData[$i].ReadThroughput=ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[1].InnerText 0
if ($awrData[$i].ReadThroughput -eq 0){$awrData[$i].ReadThroughput=(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[2].InnerText 0)+(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[3].InnerText 0)}
$awrData[$i].WriteThroughput=ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[4].InnerText 0
if ($awrData[$i].WriteThroughput -eq 0){$awrData[$i].WriteThroughput=(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[5].InnerText 0)+(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[6].InnerText 0)+(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[7].InnerText 0)}
$awrData[$i].ReadIOPS=ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[8].InnerText 0
if ($awrData[$i].ReadIOPS -eq 0){$awrData[$i].ReadIOPS=(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[9].InnerText 0)+(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[10].InnerText 0)}
$awrData[$i].WriteIOPS=ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[11].InnerText 0
if ($awrData[$i].WriteIOPS -eq 0)
{
$awrData[$i].WriteIOPS=(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[12].InnerText 0)+(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[13].InnerText 0)+(ConvertNumberOrDefault $tblIOStatByFileType.rows[$i+$offset].cells[14].InnerText 0)
}
}
}
}
else {
Write-Host "HTML table cannot be found summary=`"IOStat by File Type*`" while processing file `"$awrReportFileName`"" -ForegroundColor Red
}
Write-Debug "awrData contains $($awrData.Length) element(s)"
Write-Debug ($awrData | Format-Table | Out-String)
return $awrData
}
######################################### Process AWR Report #########################################
<#
.SYNOPSIS
Process a single AWR Report file.
.DESCRIPTION
Processes a given AWR report file in HTML format, extracts information and appends it into a global array (awrDataAll)
.PARAMETER awrReportFileName
Full path to AWR report file to be processed.
.EXAMPLE
ProcessAWRReport -awrReportFileName $_.FullName
#>
function ProcessAWRReport {
param (
[string]$awrReportFileName
)
Write-Host "$($global:numProcessedFiles+1)-Processing file : $awrReportFileName"
$OperationsStartedAt=Get-Date
try {
Write-Debug "Creating HTML DOM object..."
$html = New-Object -ComObject "HTMLFile"
Write-Debug "Opening HTML file ($awrReportFileName) ..."
$source = Get-Content -Path $awrReportFileName -Raw
Write-Debug "Loading DOM document ..."
try {
$html.IHTMLDocument2_write($source)
}
catch {
$srcBytes = [System.Text.Encoding]::Unicode.GetBytes($source)
$html.write($srcBytes)
}
$htmlHeader=$html.body.getElementsByTagName('h1')
if ($htmlHeader)
{
$awrReportFileBaseName=""
try{
$awrReportFileBaseName = [System.IO.Path]::GetFileName($awrReportFileName)
}
catch{}
if([string]::IsNullOrEmpty($awrReportFileBaseName))
{
$awrReportFileBaseName=$awrReportFileName
}
$awrData = $null
if ($htmlHeader[0].InnerText.Trim().StartsWith($global:AWRRACReportTitle))
{
$awrData=ParseAWR_RAC $html $awrReportFileBaseName
}
elseif ($htmlHeader[0].InnerText.Trim() -ieq $global:AWRNormalReportTitle) {
$awrData=ParseAWR_Normal $html $awrReportFileBaseName $false
}
elseif ($htmlHeader[0].InnerText.Trim() -ieq $global:AWRPDBReportTitle) {
$awrData=ParseAWR_Normal $html $awrReportFileBaseName $true
}
else
{
throw "This file ($awrReportFileName) does not seem to be a valid report file. It should be an AWR report generated by awrgrpt.sql or a Global (RAC) AWR report generated by awrgrpt.sql. The report should read `"$global:AWRRACReportTitle`" or `"$global:AWRNormalReportTitle`" at the top."
}
#append to global array
if($null -ne $awrData )
{
$global:numProcessedFiles++
$global:awrDataAll += $awrData
}
}
else {
throw "This file ($awrReportFileName) does not seem to be a valid report file. It should be an AWR report generated by awrgrpt.sql or a Global (RAC) AWR report generated by awrgrpt.sql. The report should read `"$global:AWRRACReportTitle`" or `"$global:AWRNormalReportTitle`" at the top."
}
}
catch {
Write-Host "Error processing file `"$awrReportFileName`":" -ForegroundColor Red
Write-Host ($_ | out-string) -ForegroundColor Red
}
finally{
Write-Debug "Releasing DOM object..."
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($html) | Out-Null
Write-Debug "File processing took $($($($(Get-Date) - $OperationsStartedAt)).TotalSeconds) seconds"
}
}
######################################### Export to Excel #########################################
<#
.SYNOPSIS
Export data to Excel
.DESCRIPTION
Export data that was collected into the global array (awrDataAll) to an excel file. Also generates Azure VM SKU recommendations and exports them into the same Excel file.
#>
function ExportToExcel(){
Write-Host "Exporting to Excel..."
Write-Debug "Starting Excel ..."
$XL = New-Object -comobject Excel.Application
if ($DebugPreference -eq "Continue")
{
$XL.Visible = $true
}
if (-not $XL.Visible) {
$XL.DisplayAlerts = $false
}
try{
$global:awrDataAll = $global:awrDataAll |
Group-Object -Property InstanceIndex,Release,DBName,InstanceName,HostName |
ForEach-object {
$MaxAAS =($_.Group | Select-Object @{n='AAS' ;e={[math]::Round($_.DBTime / $_.ElapsedTime,3)}} | Measure-Object AAS -Maximum).Maximum
$MaxAASRow = $_.Group | Select-Object ElapsedTime,DBTime,DBCPU,@{n='AAS' ;e={[math]::Round($_.DBTime / $_.ElapsedTime,3)}} | Where-Object AAS -eq $MaxAAS | Select-Object ElapsedTime,DBTime,DBCPU -First 1
$elapsedTime = $MaxAASRow.ElapsedTime
$dbTime = $MaxAASRow.DBTime
$dbCpu = $MaxAASRow.DBCPU
$CPUs,$Cores,$Memory = ($_.Group|Measure-Object CPUs,Cores,Memory -Maximum).Maximum
$SGAUse,$PGAUse = ($_.Group|Measure-Object SGAUse,PGAUse -Maximum).Maximum
$BusyCpu, $ReadThroughput, $ReadIOPS, $WriteThroughput, $WriteIOPS = ($_.Group|Measure-Object BusyCpu, ReadThroughput, ReadIOPS, WriteThroughput, WriteIOPS -Maximum).Maximum
$awrReportFileName=($_.Group.AWRReportFileName -join ",`n")
$reportType=(($_.Group.ReportType | Select-Object -Unique) -join ",")
[PSCustomObject]@{
InstanceIndex=$_.Group[0].InstanceIndex
Release =$_.Group[0].Release
DBName =$_.Group[0].DBName
InstanceName =$_.Group[0].InstanceName
HostName =$_.Group[0].HostName
ElapsedTime =$elapsedTime
DBTime =$dbTime
DBCPU =$dbCpu
CPUs =$CPUs
Cores =$Cores
Memory =$Memory
BusyCPU =$BusyCpu
SGAUse =$SGAUse
PGAUse =$PGAUse
ReadThroughput =$ReadThroughput
WriteThroughput =$WriteThroughput
ReadIOPS =$ReadIOPS
WriteIOPS =$WriteIOPS
TotalThroughput =0
TotalIOPS =0
CPUTotalCapacity=0
ORAUse =0
SourceCPUHTFactor=0
AverageActiveSessions=0
AWRReportFileName=$awrReportFileName
ReportType=$reportType
}
}
Write-Debug "Opening workbook..."
Copy-Item $TemplateFileName $global:outputExcel
$file=Get-Item $global:outputExcel #now that the file exists, get the file object
$global:outputExcel=$file.FullName
$wbOma = $XL.Workbooks.Open($global:outputExcel, $false) #UpdateLinks:=false
$wsOma = $wbOma.Worksheets.Item("Data")
if (-not $NoAwr.IsPresent)
{
Write-Host "Exporting tables ..."
$OperationsStartedAt=Get-Date
$tblAwr = $wsOma.ListObjects.Item("AWRData")
Write-Debug "Populating AWR table..."
ResetTable $tblAwr
if($global:awrDataAll.Length -gt 0)
{
[array]$props=$global:awrDataAll[0].psobject.properties | select Name
}
for($i=0;$i -lt $global:awrDataAll.Length;$i++) {
AppendRow $tblAwr
for($j=0;$j -lt $props.Length;$j++) {
if (-not $tblAwr.DataBodyRange.Item($i+1,$j+1).HasFormula())
{
$value = $global:awrDataAll[$i]."$($props[$j].Name)"
$tblAwr.DataBodyRange.Item($i+1,$j+1) = $value
if($props[$j].Name -eq "AWRReportFileName")
{
$tblAwr.DataBodyRange.Item($i+1,$j+1).WrapText = $false
}
}
}
}
############################# Instance Summary #############################
$tblInstSummary = $wsOma.ListObjects.Item("InstSummary")
Write-Debug "Populating Instance Summary table..."
[array]$instSummaryData = $global:awrDataAll | Select-Object DBName,InstanceName -Unique
if($instSummaryData.Length -gt 0)
{
[array]$props=$instSummaryData[0].psobject.properties | select Name
}
ResetTable $tblInstSummary
for($i=0;$i -lt $instSummaryData.Length;$i++) {
AppendRow $tblInstSummary
for($j=0;$j -lt $props.Length;$j++) {
if (-not $tblInstSummary.DataBodyRange.Item($i+1,$j+1).HasFormula())
{
$tblInstSummary.DataBodyRange.Item($i+1,$j+1) = $instSummaryData[$i]."$($props[$j].Name)"
}
}
}
############################# Host Summary #############################
$tblHostSummary = $wsOma.ListObjects.Item("HostSummary")
Write-Debug "Populating Host Summary table..."
[array]$hostSummaryData = $global:awrDataAll | Select-Object HostName -Unique
if($hostSummaryData.Length -gt 0)
{
[array]$props=$hostSummaryData[0].psobject.properties | select Name
}
ResetTable $tblHostSummary
for($i=0;$i -lt $hostSummaryData.Length;$i++) {
AppendRow $tblHostSummary
for($j=0;$j -lt $props.Length;$j++) {
if (-not $tblHostSummary.DataBodyRange.Item($i+1,$j+1).HasFormula())
{
$tblHostSummary.DataBodyRange.Item($i+1,$j+1) = $hostSummaryData[$i]."$($props[$j].Name)"
}
}
}
############################# DB Summary #############################
$tblDBSummary = $wsOma.ListObjects.Item("DBSummary")
Write-Debug "Populating DB Summary table..."
[array]$dbSummaryData = $global:awrDataAll | Select-Object DBName -Unique
if($dbSummaryData.Length -gt 0)
{
[array]$props=$dbSummaryData[0].psobject.properties | select Name
}
ResetTable $tblDBSummary
for($i=0;$i -lt $dbSummaryData.Length;$i++) {
AppendRow $tblDBSummary
$tblDBSummary.DataBodyRange.Item($i+1,1) = "Server1"
for($j=0;$j -lt $props.Length;$j++) {
if (-not $tblDBSummary.DataBodyRange.Item($i+1,$j+1).HasFormula())
{
$tblDBSummary.DataBodyRange.Item($i+1,$j+1+1) = $dbSummaryData[$i]."$($props[$j].Name)"
}
}
}
Write-Debug "Export tables took $($($($(Get-Date) - $OperationsStartedAt)).TotalSeconds) seconds"
}
else{
$temp = $XL.DisplayAlerts
$XL.DisplayAlerts = $false
$wsOma.Delete() | Out-Null
$XL.DisplayAlerts = $temp
$wsOma=$null
$wsRecommendations = $wbOma.Worksheets.Item("Recommendations")
$tblAzureServerSummary = $wsRecommendations.ListObjects.Item("AzureServerSummary")
$tblAzureServerSummary.DataBodyRange.Item(1,1) = "Server1"
$tblAzureServerSummary.DataBodyRange.Item(1,2) = ""
$tblAzureServerSummary.DataBodyRange.Item(1,3) = "<not used>"
$tblAzureServerSummary.DataBodyRange.Item(1,4) = "<not used>"
$tblAzureServerSummary.DataBodyRange.Item(1,5) = "<enter data>"
$tblAzureServerSummary.DataBodyRange.Item(1,6) = "<not used>"
$tblAzureServerSummary.DataBodyRange.Item(1,7) = "<not used>"
$tblAzureServerSummary.DataBodyRange.Item(1,8) = "<enter data>"
$tblAzureServerSummary.DataBodyRange.Item(1,9) = "<enter data>"
$tblAzureServerSummary.DataBodyRange.Item(1,10) = "<not used>"
$tblAzureServerSummary.DataBodyRange.Item(1,11) = "<enter data>"
$tblAzureServerSummary.DataBodyRange.Item(1,12) = "<enter data>"
for($i=1;$i -lt $tblAzureServerSummary.DataBodyRange.Columns.Count;$i++) {
if($tblAzureServerSummary.DataBodyRange.Item(1,$i+1).Text -eq "<not used>")
{
$tblAzureServerSummary.DataBodyRange.Item(1,$i+1).Font.ThemeColor = 3 #xlThemeColorDark2
$tblAzureServerSummary.DataBodyRange.Item(1,$i+1).Font.TintAndShade = -0.5 #50% lighter
$tblAzureServerSummary.DataBodyRange.Item(1,$i+1).HorizontalAlignment = -4108 #xlCenter
}
}
}
if ([string]::IsNullOrEmpty($AzureRegion))
{
$filter="currencyCode eq 'USD' and (serviceFamily eq 'Compute' or serviceFamily eq 'Storage')"
Write-Host "Fetching pricelist for all regions in USD. This operation takes a while."
}
else {
$filter="armRegionName eq '$AzureRegion' and currencyCode eq 'USD' and (serviceFamily eq 'Compute' or serviceFamily eq 'Storage')"
Write-Host "Fetching pricelist for $AzureRegion in USD. This operation takes a while."
}
$priceListAPIBaseUrl="https://prices.azure.com/api/retail/prices"
$url="$($priceListAPIBaseUrl)?`$filter=$filter"
$priceList=$null
$pageNum=0
$OperationsStartedAt=Get-Date
while (-not [string]::IsNullOrEmpty($url))
{
$data=Invoke-RestMethod -Uri $url -Method Get
$priceList+=$data.Items
$url=$data.NextPageLink
$pageNum++
if(($pageNum % 10) -eq 0)
{
Write-Debug "Processing $pageNum pages in $($($($(Get-Date) - $OperationsStartedAt)).TotalSeconds) seconds."
}
}
Write-Debug "Processed $pageNum pages in $($($($(Get-Date) - $OperationsStartedAt)).TotalSeconds) seconds."
$OperationsStartedAt=Get-Date
$wsAzurePriceList = $wbOma.Worksheets.Item("AzurePriceList")
$priceListProperties=$priceList[0].psobject.properties | select Name
$priceListStaticProperties=[string[]]("serviceFamily","serviceName","type","productName","armSkuName","skuName","meterName", "unitPrice","unitOfMeasure","armRegionName","location")
Write-Debug "Creating AzurePriceList table ..."
try{$tblAzurePriceList = $wsAzurePriceList.ListObjects.Item("AzurePriceList")}catch{}
if($null -eq $tblAzurePriceList)
{
$tblStartRow=1
$tblStartCol=1
$tblAzurePriceList = $wsAzurePriceList.ListObjects.Add(
$global:XlListObjectSourceType_xlSrcRange,
$wsAzurePriceList.Range($wsAzurePriceList.cells($tblStartRow,$tblStartCol),$wsAzurePriceList.cells($tblStartRow+$priceList.Length ,$priceListProperties.Length+$tblStartCol-1)),
$null ,
$global:XlYesNoGuess_xlYes)
$tblAzurePriceList.Name = "AzurePriceList"
$tblAzurePriceList.ShowHeaders = $true
$tblAzurePriceList.ShowTotals = $false
$tblAzurePriceList.TableStyle = "TableStyleMedium9"
}
else {
try{
if($null -ne $tblAzurePriceList.DataBodyRange) {$tblAzurePriceList.DataBodyRange.Rows.Delete() | Out-Null}
if($priceListProperties.Length -lt $tblAzurePriceList.ListColumns.Count)
{
for($i=$priceListProperties.Length+1;$i -le $tblAzurePriceList.ListColumns.Count;$i++) {
$tblAzurePriceList.HeaderRowRange.Item(1,$i) = ''
}
}
$tblStartRow=$tblAzurePriceList.Range.Cells(1,1).Row
$tblStartCol=$tblAzurePriceList.Range.Cells(1,1).Column
$tblAzurePriceList.Resize($wsAzurePriceList.Range($wsAzurePriceList.cells($tblStartRow,$tblStartCol),$wsAzurePriceList.cells($tblStartRow+$priceList.Length ,$priceListProperties.Length+$tblStartCol-1)))
}
catch{}
}
Write-Debug "Formatting headers for AzurePriceList table..."
for($i=0;$i -lt $priceListStaticProperties.Length;$i++) {
$tblAzurePriceList.HeaderRowRange.Item(1,$i+1) = $priceListStaticProperties[$i]
}
$k=$priceListStaticProperties.Length
for($i=0;$i -lt $priceListProperties.Length;$i++) {
if($priceListStaticProperties -notcontains $priceListProperties[$i].Name)
{
$tblAzurePriceList.HeaderRowRange.Item(1,(($k++)+1)) = $priceListProperties[$i].Name
}
}
if($priceList.Length -gt 0)
{
Write-Host "Populating AzurePriceList table..."
$rangeValues = new-object 'Object[,]' $priceList.Length, $priceListProperties.Length
for($i=0;$i -lt $priceList.Length;$i++) {
for($j=0;$j -lt $priceListStaticProperties.Length;$j++) {
$rangeValues[$i,$j] = $priceList[$i]."$($priceListStaticProperties[$j])"
}
$k=$priceListStaticProperties.Length
for($j=0;$j -lt $priceListProperties.Length;$j++) {
if($priceListStaticProperties -notcontains $priceListProperties[$j].Name)
{
$rangeValues[$i,($k++)] = $priceList[$i]."$($priceListProperties[$j].Name)"
}
}
}
$tblAzurePriceList.DataBodyRange.Value = $rangeValues
}
else{
Write-Host "Could not fetch prices."
}
Write-Debug "Operation took $($($($(Get-Date) - $OperationsStartedAt)).TotalSeconds) seconds."
Write-Host "Fetching available Azure VM and Disk Skus in $AzureRegion. This operation takes a while."
try{
$OperationsStartedAt=Get-Date
# Problem is that JSON reurned from "az vm list-skus" call can include two attributes with the same name but diferent casing: "locationInfo.zoneDetails.Name" and
# "locationInfo.zoneDetails.name". Therefore "ConvertFrom-Json" raises an error. In order to solve this problem properly, "-AsHashtable" parameter is provided for
# "ConvertFrom-Json".
# However "-AsHashTable" parameter works only for PowerShell v6 and above. At the moment, we do not need the "Name" attribute in JSON anyway.
# Implemented a workaround so that instead of calling "ConvertFrom-Json" with "-AsHashtable" attribute, we'll just rename the attribute from "Name" to "_Name"
# This workaround will also remove the need to upgrade PowerShell installation.
# Original Code: $json=az vm list-skus --all --location $AzureRegion | ConvertFrom-Json -AsHashtable
if ([string]::IsNullOrEmpty($AzureRegion))
{
$stemp=az vm list-skus --all 2>$null
}
else {
$stemp=az vm list-skus --all --location $AzureRegion 2>$null
}
$stemp=$stemp -creplace "`"Name`"","`"_Name`""
$json= $stemp | ConvertFrom-Json
# workaround ends here
if ($null -ne $json)
{
$numFixedProps=25
$azureVMSkus = $json | where {$_.resourceType -eq "virtualMachines"} | ForEach-Object {
$skuProps=ParseSkuSizeString($_.size)
$objectProps=[ordered]@{
name = $_.name
size = $_.size
tier = $_.tier
family=$_.family
resourceType=$_.resourceType
location=$_.locations[0]
monthlyCost=0
vmRecommendationPriority = $global:RecommendationPriority_AllOthers
vCPUs=[int]0
vCPUsAvailable=[int]0
vCPUsPerCore=[int]0
Class = $skuProps.Family
Subclass = $skuProps.Subfamily
AMDProcessor = $skuProps.AMDProcessor
BlockStoragePerformance = $skuProps.BlockStoragePerformance
Diskful = $skuProps.Diskful
IsolatedSize = $skuProps.IsolatedSize
LowMemory = $skuProps.LowMemory
MemoryIntensive = $skuProps.MemoryIntensive
ARMProcessor = $skuProps.ARMProcessor
TinyMemory = $skuProps.TinyMemory
PremiumStorage = $skuProps.PremiumStorage
AcceleratorType = $skuProps.AcceleratorType
Version = $skuProps.Version
Promo = $skuProps.Promo
}
$obj = New-Object -TypeName PSCustomObject -Property $objectProps
$_.capabilities | where {-not [string]::IsNullOrEmpty($_.name)} | ForEach-Object {
if (-not ([bool](Get-member -Name $_.name -InputObject $obj -MemberType NoteProperty)))
{
add-member -InputObject $obj -MemberType NoteProperty -Name $_.name -Value $_.value
}
else {
$obj."$($_.name)"=$_.value
}
}
$_.locationInfo[0].zoneDetails.capabilities | where {-not [string]::IsNullOrEmpty($_.name)} | ForEach-Object {
if (-not ([bool](Get-member -Name $_.name -InputObject $obj -MemberType NoteProperty)))
{
add-member -InputObject $obj -MemberType NoteProperty -Name $_.name -Value $_.value
}
else {
$obj."$($_.name)"=$_.value
}
}
return $obj
}
foreach($obj in $azureVMSkus)
{
if(($obj.Class -eq 'E') -and ($obj.Subclass -ne 'C') -and
($obj.Diskful) -and (-not $obj.LowMemory) -and (-not $obj.TinyMemory) -and
($obj.PremiumIO -ieq "True"))
{
if (($obj.Version -eq ($azureVMSkus | Where-Object {($_.Class -eq $obj.Class) -and ($_.vCPUs -eq $obj.vCPUs)} | Measure-Object Version -Maximum).Maximum) -or
(($azureVMSkus | Where-Object {
($_.Version -gt $obj.Version) -and ($_.Class -eq $obj.Class) -and
($_.vCPUs -eq $obj.vCPUs) -and (-not $_.BlockStoragePerformance) -and
(-not $_.ARMProcessor) -and (-not $_.LowMemory) -and
(-not $_.TinyMemory) -and ($_.PremiumIO -ieq "True") }).Length -eq 0))
{
$obj.vmRecommendationPriority=$global:RecommendationPriority_BestPracticesOnly
}
}
elseif(($obj.Class -eq 'M') -and
(-not $obj.LowMemory) -and (-not $obj.TinyMemory) -and
($obj.PremiumIO -ieq "True"))
{
if (($obj.Version -eq ($azureVMSkus | Where-Object {($_.Class -eq $obj.Class) -and ($_.vCPUs -eq $obj.vCPUs)} | Measure-Object Version -Maximum).Maximum) -or
(($azureVMSkus | Where-Object {
($_.Version -gt $obj.Version) -and ($_.Class -eq $obj.Class) -and
($_.vCPUs -eq $obj.vCPUs) -and (-not $_.BlockStoragePerformance) -and
(-not $_.ARMProcessor) -and (-not $_.LowMemory) -and
(-not $_.TinyMemory) -and ($_.PremiumIO -ieq "True") }).Length -eq 0))
{
$obj.vmRecommendationPriority=$global:RecommendationPriority_BestPracticesOnly
}
}
}
$wsAzureVmSkus = $wbOma.Worksheets.Item("AzureVMSkus")
Write-Debug "Fetching VM properties ..."
$props=$azureVMSkus[0].psobject.properties | select Name
$props1=$props[0 .. ($numFixedProps-1)]
$props2=$props[($numFixedProps) .. ($props.Length-1)]
for($i=1;$i -lt $azureVMSkus.Length;$i++)
{
$props=$azureVMSkus[$i].psobject.properties | select Name
$props2+=$props[($numFixedProps) .. ($props.Length-1)]
if(($i % 100) -eq 0)
{
$props2 = $props2 | select Name -Unique
}
}
$vmSkuProperties = $props1+($props2 | select Name -Unique)
Write-Debug "Setting VM costs ..."
$Timer1=Get-Date
$reducedPriceListArray = $priceList |
Where-Object {
($_.serviceName -eq 'Virtual Machines') -and #only 'Virtual Machines' i.e. no 'Cloud Services'
($_.type -eq 'Consumption') -and #only 'Consumption' prices i.e not 'DevTestConsumption'
($_.skuName.EndsWith('Low Priority') -eq $false) -and #dont consider low priority prices
($_.skuName.EndsWith('Spot') -eq $false) -and # dont consider spot prices
($_.productName.EndsWith('Cloud Services') -eq $false) -and # dont consider cloud services
($_.productName.StartsWith('Cloud Services') -eq $false) -and # dont consider cloud services
($_.productName.EndsWith('CloudServices') -eq $false) -and # dont consider cloud services
($_.productName.StartsWith('CloudServices') -eq $false) -and # dont consider cloud services
($_.productName.EndsWith('Windows') -eq $false) #
}
if ($DebugPreference -eq "Continue")
{
foreach($p in ($reducedPriceListArray | Group-Object -Property armSkuName | Where-Object {$_.Count -gt 1}))
{
Write-Debug "More than one price found for VM SKU '$($p.Name)'"
}
}
for($i=0;$i -lt $azureVMSkus.Length;$i++) {
[array]$r=$reducedPriceListArray | where {($azureVMSkus[$i].name -eq $_.armSkuName)}
if($r.Length -eq 1)
{
$azureVMSkus[$i].monthlyCost = $r[0].unitPrice*730
}
elseif($r.Length -gt 1)
{
$azureVMSkus[$i].monthlyCost = -1
}
}
Write-Debug "Setting VM costs took $($($($(Get-Date) - $Timer1)).TotalSeconds) seconds."
Write-Debug "Creating AzureVMSkus table ..."
try{$tblAzureVmSkus = $wsAzureVmSkus.ListObjects.Item("AzureVMSkus")}catch{}
if($null -eq $tblAzureVmSkus)
{
$tblStartRow=1
$tblStartCol=1
$tblAzureVmSkus = $wsAzureVmSkus.ListObjects.Add(
$global:XlListObjectSourceType_xlSrcRange,
$wsAzureVmSkus.Range($wsAzureVmSkus.cells($tblStartRow,$tblStartCol),$wsAzureVmSkus.cells($tblStartRow+$azureVMSkus.Length ,$vmSkuProperties.Length+$tblStartCol-1)),
$null ,
$global:XlYesNoGuess_xlYes)
$tblAzureVmSkus.Name = "AzureVMSkus"
$tblAzureVmSkus.ShowHeaders = $true
$tblAzureVmSkus.ShowTotals = $false
$tblAzureVmSkus.TableStyle = "TableStyleMedium9"
}
else {
try{
if($null -ne $tblAzureVmSkus.DataBodyRange) {$tblAzureVmSkus.DataBodyRange.Rows.Delete() | Out-Null}
if($vmSkuProperties.Length -lt $tblAzureVmSkus.ListColumns.Count)
{
for($i=$vmSkuProperties.Length+1;$i -le $tblAzureVmSkus.ListColumns.Count;$i++) {
$tblAzureVmSkus.HeaderRowRange.Item(1,$i) = ''
}
}
$tblStartRow=$tblAzureVmSkus.Range.Cells(1,1).Row
$tblStartCol=$tblAzureVmSkus.Range.Cells(1,1).Column
$tblAzureVmSkus.Resize($wsAzureVmSkus.Range($wsAzureVmSkus.cells($tblStartRow,$tblStartCol),$wsAzureVmSkus.cells($tblStartRow+$azureVMSkus.Length ,$vmSkuProperties.Length+$tblStartCol-1)))
}
catch{}
}
Write-Debug "Formatting headers for AzureVMSkus table..."
for($i=0;$i -lt $vmSkuProperties.Length;$i++) {
$tblAzureVmSkus.HeaderRowRange.Item(1,$i+1) = $vmSkuProperties[$i].Name
}
Write-Host "Populating AzureVMSkus table..."
$rangeValues = new-object 'Object[,]' $azureVMSkus.Length, $vmSkuProperties.Length
for($i=0;$i -lt $azureVMSkus.Length;$i++) {
for($j=0;$j -lt $vmSkuProperties.Length;$j++) {
$rangeValues[$i,$j] = $azureVMSkus[$i]."$($vmSkuProperties[$j].Name)"
#Some values come as null from Azure RM
#updating those values with specs from https://learn.microsoft.com/en-us/azure/virtual-machines/mv2-series
if (($azureVMSkus[$i].size -like 'M208*') -and ($vmSkuProperties[$j].Name -eq 'UncachedDiskIOPS') -and ([string]::IsNullOrEmpty($azureVMSkus[$i]."$($vmSkuProperties[$j].Name)")))
{$rangeValues[$i,$j] = 40000}
elseif (($azureVMSkus[$i].size -like 'M208*') -and ($vmSkuProperties[$j].Name -eq 'UncachedDiskBytesPerSecond') -and ([string]::IsNullOrEmpty($azureVMSkus[$i]."$($vmSkuProperties[$j].Name)")))
{$rangeValues[$i,$j] = 1000000000}
elseif (($azureVMSkus[$i].size -like 'M416*') -and ($vmSkuProperties[$j].Name -eq 'UncachedDiskIOPS') -and ([string]::IsNullOrEmpty($azureVMSkus[$i]."$($vmSkuProperties[$j].Name)")))
{$rangeValues[$i,$j] = 80000}
elseif (($azureVMSkus[$i].size -like 'M416*') -and ($vmSkuProperties[$j].Name -eq 'UncachedDiskBytesPerSecond') -and ([string]::IsNullOrEmpty($azureVMSkus[$i]."$($vmSkuProperties[$j].Name)")))
{$rangeValues[$i,$j] = 2000000000}
if (($vmSkuProperties[$j].Name -eq 'vCPUsAvailable') -and ([string]::IsNullOrEmpty($azureVMSkus[$i]."$($vmSkuProperties[$j].Name)")))
{$rangeValues[$i,$j] = $azureVMSkus[$i].vCpus}
}
}
$tblAzureVmSkus.DataBodyRange.Value = $rangeValues
$numFixedProps=8
$azureDiskSkus = $json | where {$_.resourceType -eq "disks"} | ForEach-Object {
$objectProps=[ordered]@{
name = $_.name
size = $_.size
tier = $_.tier
family=$_.family
resourceType=$_.resourceType
location=$_.locations[0]
monthlyCost=0
diskRecommendationPriority = $global:RecommendationPriority_AllOthers
}
$obj = New-Object -TypeName PSCustomObject -Property $objectProps
$_.capabilities | where {-not [string]::IsNullOrEmpty($_.name)} | ForEach-Object {
if (-not ([bool](Get-member -Name $_.name -InputObject $obj -MemberType NoteProperty)))
{
add-member -InputObject $obj -MemberType NoteProperty -Name $_.name -Value $_.value
}
else {
$obj."$($_.name)"=$_.value
}
}
$_.locationInfo[0].zoneDetails.capabilities | where {-not [string]::IsNullOrEmpty($_.name)} | ForEach-Object {
if (-not ([bool](Get-member -Name $_.name -InputObject $obj -MemberType NoteProperty)))
{
add-member -InputObject $obj -MemberType NoteProperty -Name $_.name -Value $_.value
}
else {
$obj."$($_.name)"=$_.value
}
}
return $obj
}
$wsAzureDiskSkus = $wbOma.Worksheets.Item("AzureDiskSkus")
$props=$azureDiskSkus[0].psobject.properties | select Name
$props1=$props[0 .. ($numFixedProps-1)]
$props2=$props[($numFixedProps) .. ($props.Length-1)]
for($i=1;$i -lt $azureDiskSkus.Length;$i++)
{
$props=$azureDiskSkus[$i].psobject.properties | select Name
$props2+=$props[($numFixedProps) .. ($props.Length-1)]
if(($i % 100) -eq 0)
{
$props2 = $props2 | select Name -Unique
}
}
$diskSkuProperties = $props1+($props2 | select Name -Unique)
foreach($obj in $azureDiskSkus)
{
if ($obj.name.StartsWith("PremiumV2"))
{
$obj.diskRecommendationPriority=$global:RecommendationPriority_BestPracticesOnly
}
elseif ($obj.name.StartsWith("Premium"))
{
$obj.diskRecommendationPriority=$global:RecommendationPriority_BestPracticesOnly
}
elseif ($obj.name.StartsWith("Ultra"))
{
$obj.diskRecommendationPriority=$global:RecommendationPriority_BestPracticesOnly
}
}
Write-Debug "Creating AzureDiskSkus table ..."
try{$tblAzureDiskSkus = $wsAzureDiskSkus.ListObjects.Item("AzureDiskSkus")}catch{}
if($null -eq $tblAzureDiskSkus)
{
$tblStartRow=1
$tblStartCol=1
$tblAzureDiskSkus = $wsAzureDiskSkus.ListObjects.Add(
$global:XlListObjectSourceType_xlSrcRange,
$wsAzureDiskSkus.Range($wsAzureDiskSkus.cells($tblStartRow,$tblStartCol),$wsAzureDiskSkus.cells($tblStartRow+$azureDiskSkus.Length ,$diskSkuProperties.Length+$tblStartCol-1)),
$null ,
$global:XlYesNoGuess_xlYes)
$tblAzureDiskSkus.Name = "AzureDiskSkus"
$tblAzureDiskSkus.ShowHeaders = $true
$tblAzureDiskSkus.ShowTotals = $false
$tblAzureDiskSkus.TableStyle = "TableStyleMedium9"
}
else {
try{
if($null -ne $tblAzureDiskSkus.DataBodyRange) {$tblAzureDiskSkus.DataBodyRange.Rows.Delete() | Out-Null}
if($diskSkuProperties.Length -lt $tblAzureDiskSkus.ListColumns.Count)
{
for($i=$diskSkuProperties.Length+1;$i -le $tblAzureDiskSkus.ListColumns.Count;$i++) {
$tblAzureDiskSkus.HeaderRowRange.Item(1,$i) = ''
}
}
$tblStartRow=$tblAzureDiskSkus.Range.Cells(1,1).Row
$tblStartCol=$tblAzureDiskSkus.Range.Cells(1,1).Column
$tblAzureDiskSkus.Resize($wsAzureDiskSkus.Range($wsAzureDiskSkus.cells($tblStartRow,$tblStartCol),$wsAzureDiskSkus.cells($tblStartRow+$azureDiskSkus.Length ,$diskSkuProperties.Length+$tblStartCol-1)))
}
catch{}
}
Write-Debug "Formatting headers for AzureDiskSkus table..."
for($i=0;$i -lt $diskSkuProperties.Length;$i++) {
$tblAzureDiskSkus.HeaderRowRange.Item(1,$i+1) = $diskSkuProperties[$i].Name
}
Write-Host "Populating AzureDiskSkus table..."
$rangeValues = new-object 'Object[,]' $azureDiskSkus.Length, $diskSkuProperties.Length
for($i=0;$i -lt $azureDiskSkus.Length;$i++) {
for($j=0;$j -lt $diskSkuProperties.Length;$j++) {
$rangeValues[$i,$j] = $azureDiskSkus[$i]."$($diskSkuProperties[$j].Name)"
}
}
$tblAzureDiskSkus.DataBodyRange.Value = $rangeValues
Write-Debug "Operation took $($($($(Get-Date) - $OperationsStartedAt)).TotalSeconds) seconds"
foreach($slicerCache in $wbOma.SlicerCaches)
{
foreach($slicer in $slicerCache.Slicers)
{
if($slicer.Name -eq "vmRecommendationPriority" -or $slicer.Name -eq "diskRecommendationPriority")
{
foreach($slicerItem in $slicerCache.SlicerItems)
{
if($slicerItem.Value -eq $global:RecommendationPriority_BestPracticesOnly)
{
$slicerItem.Selected = $true
}
else {
$slicerItem.Selected = $false
}
}
}
}
}
if(-not $NoAwr.IsPresent)
{
Write-Host "Refreshing recommendations..."
$XL.Run('RefreshRecommendations')
}
}
else
{
Write-Host "Azure SKUs cannot be fetched. Parsed AWR data will be available in Excel, but recommendations will not be available."
}
}
catch {
Write-Host "Error generating recommendations." -ForegroundColor Red
Write-Host ($_ | out-string) -ForegroundColor Red
}
}
catch [System.Runtime.InteropServices.COMException] {
Write-Host "An Excel related error has occured." -ForegroundColor Red
Write-Host ($_ | out-string) -ForegroundColor Red
}
catch {
Write-Host ($_ | out-string) -ForegroundColor Red
}
finally
{
Write-Debug "Saving Excel file ($global:outputExcel)..."
if($null -ne $wbOma)
{
if($null -ne $wsOma)
{
$wsOma.Activate() | Out-Null
$wsOma.Cells(1,1).Activate() | Out-Null
}
elseif($null -ne $wsRecommendations)
{
$wsRecommendations.Activate() | Out-Null
$wsRecommendations.Cells(1,1).Activate() | Out-Null
}
$wbOma.Save() | Out-Null #($global:outputExcel,$global:XlFileFormat_xlOpenXMLWorkbookMacroEnabled)
$wbOma.Close($false) | Out-Null
Unblock-File -Path $global:outputExcel
}
Write-Debug "Closing Excel ..."
if($null -ne $XL)
{
$XL.Quit() | Out-Null
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsOma) | Out-Null}catch{}
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsRecommendations) | Out-Null}catch{}
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsAzurePriceList) | Out-Null}catch{}
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsAzureVmSkus) | Out-Null}catch{}
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsAzureDiskSkus) | Out-Null}catch{}
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wbOma) | Out-Null}catch{}
try{[System.Runtime.Interopservices.Marshal]::ReleaseComObject($XL) | Out-Null}catch{}
[System.GC]::Collect() | Out-Null
[System.GC]::WaitForPendingFinalizers() | Out-Null
}
}
}
######################################### Init global variables #########################################
$global:XlListObjectSourceType_xlSrcRange=1
$global:XlYesNoGuess_xlYes=1
[array]$global:awrDataAll=$null
$global:AWRRACReportTitle="WORKLOAD REPOSITORY REPORT (RAC)"
$global:AWRNormalReportTitle="WORKLOAD REPOSITORY report for"
$global:AWRPDBReportTitle="WORKLOAD REPOSITORY PDB report (root snapshots)"
$global:numProcessedFiles=0
$global:RecommendationPriority_BestPracticesOnly="Best Practices Only"
$global:RecommendationPriority_AllOthers="All Others"
$azureSizeStringRegExPattern="([A-Z])([A-Z])?([0-9]+)(\-[0-9]+)?([abdilmprst]*)(_[A-Z,0-9]+)?(_v[0-9]+)?(_Promo)?"
$global:azureSizeStringRegex = [regex]$azureSizeStringRegExPattern
######################################### MAIN #########################################
if($Help -eq $true)
{
Write-Host "Usage: $($MyInvocation.MyCommand.Name) [OPTIONS]"
Write-Host "OPTIONS:"
Write-Host " -h, Help : Display this screen."
Write-Host " -SourceFolder : Source folder that contains AWR reports in HTML format. Default is '.' (current directory)."
Write-Host " -NoAwr : Creates an empty file so that you can manually enter vCPU, memory and disk requirements and generate recommendations."
Write-Host " -OutputFile : Full path of the Excel file that will be created as output. Default is same name as SourceFolder directory name with XLSM extension under SourceFolder directory."
Write-Host " -TemplateFileName : Excel templatethat will be used for capacity estimations. Default is '.\template.xlsm'."
Write-Host " -AzureRegion : Name of the Azure region to be used when generating Azure resource recommendations. Default is 'westus'."
Write-Host " -Debug : Generates debug output."
Write-Host ""
Write-Host "$($MyInvocation.MyCommand.Name) -SourceFolder `"C:\AWR`" -AzureRegion `"westus`""
Exit 0
}
if ($PSBoundParameters['Debug']) {
$DebugPreference = 'Continue'
}
[datetime]$ScriptStartAt = Get-Date
Write-Debug "Starting..."
Write-Debug "DebugPreference=$DebugPreference"
if ([bool](Get-Command -Name 'az' -ErrorAction SilentlyContinue) -eq $false) { #check if azure cli is installed
Write-Host "Azure CLI is not installed." -ForegroundColor Red
Write-Host "Please install Azure CLI from https://aka.ms/installazurecliwindows" -ForegroundColor Red
Write-Host "Then run 'az-login' and re-execute this script." -ForegroundColor Red
Exit
}
if (-not (Test-Path $SourceFolder -PathType Container))
{
Write-Host "Source folder not found: $SourceFolder" -ForegroundColor Red
Write-Host "Exiting."
Exit
}
if(-not $NoAwr.IsPresent)
{
$src=Get-Item $sourceFolder
$sourceFolder=$src.FullName
if([string]::IsNullOrEmpty($OutputFile))
{
$global:outputExcel="$($src.FullName)\$($src.Name).xlsm"
}
else {
if($OutputFile -notlike "*.xlsm")
{
$OutputFile = "$OutputFile.xlsm"
}
if(-not $OutputFile.Contains("\"))
{
$OutputFile = "$($src.FullName)\$OutputFile"
}
$global:outputExcel=$OutputFile
}
}
else
{
if([string]::IsNullOrEmpty($OutputFile))
{
$rnd=Get-Random -Minimum 1000 -Maximum 9999
$global:outputExcel=".\OMAT-NoAwr-$rnd.xlsm"
}
else {
if($OutputFile -notlike "*.xlsm")
{
$OutputFile = "$OutputFile.xlsm"
}
if(-not $OutputFile.Contains("\"))
{
$OutputFile = ".\$OutputFile"
}
$global:outputExcel=$OutputFile
}
}
$azureAccountJson=az account show 2>$null
if($null -eq $azureAccountJson)
{
Write-Host "You need to be logged on to Azure to run this script." -ForegroundColor Red
Write-Host "Please run 'az login' first and rerun this script." -ForegroundColor Red
Exit
}
else {
$azureAccount = $azureAccountJson | ConvertFrom-Json
Write-Host "Connected to subscription '$($azureAccount.name)' ($($azureAccount.id)) as '$($azureAccount.user.name)'"
}
$AzureRegion = $AzureRegion.ToLower()
$azureRegionFound=((az account list-locations 2>$null) | ConvertFrom-Json | Where-Object {$_.name -eq $AzureRegion})
if($null -eq $azureRegionFound)
{
Write-Host "'$AzureRegion' is not a valid Azure region identifier or your current subscription '$($azureAccount.id)' does not have access to '$AzureRegion'." -ForegroundColor Red
Exit
}
else {
Write-Host "Using Azure region '$AzureRegion'"
}
if($NoAwr.IsPresent)
{
Write-Host "No AWR files given. You will need to manually enter required values in the output Excel generated."
}
else
{
Write-Host "Processing files from directory : $SourceFolder"
}
if (-not (Test-Path $TemplateFileName -PathType Leaf))
{
throw "Template file not found: $TemplateFileName"
}
else
{
$tmpfile=Get-Item $TemplateFileName
Write-Host "Using template file : $($tmpFile.FullName)"
}
if (Test-Path $global:outputExcel -PathType Leaf)
{
if ($DebugPreference -eq "Continue")
{
Write-Host "Overwriting output file : $global:outputExcel"
Remove-Item -LiteralPath $global:outputExcel
}
else {
Write-Host "Output file already exists : $global:outputExcel" -ForegroundColor Red
Write-Host "Exiting." -ForegroundColor Red
Exit
}
}
if (-not $NoAwr.IsPresent)
{
Get-ChildItem -Path $sourceFolder -File -Filter *.html | ForEach-Object {ProcessAWRReport -awrReportFileName $_.FullName}
}
if($global:numProcessedFiles -gt 0 -or $NoAwr.IsPresent)
{
ExportToExcel | Out-Null
if($NoAwr.IsPresent)
{
Write-Host "Created file `"$global:outputExcel`" with no AWR data, open the file and manually enter vCPU, memory and disk requirements in `"Azure Server Summary`" table." -ForegroundColor Green
}
else
{
Write-Host "Finished processing files from directory : $SourceFolder in $($($($(Get-Date) - $ScriptStartAt)).TotalSeconds) seconds" -ForegroundColor Green
Write-Host "Open the Excel file `"$global:outputExcel`" to review recommendations." -ForegroundColor Green
}
Write-Host "Note macros are required to be enabled in Excel. See following link to enable macros: https://support.microsoft.com/en-us/office/enable-or-disable-macros-in-microsoft-365-files-12b036fd-d140-4e74-b45e-16fed1a7e5c6" -ForegroundColor Green
}
else {
Write-Host "No AWR report files found in directory : $SourceFolder" -ForegroundColor Red
Write-Host "If you would like to create an empty file and manually enter requirements, use -NoAwr switch to run the tool." -ForegroundColor Red
Write-Host "Nothing to process. Exiting." -ForegroundColor Red
}
#global error handler
trap {
Write-Host "Error occured." -ForegroundColor Red
Write-Host ($_ | out-string) -ForegroundColor Red
break
}