update
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
# It all starts with a single line of powershell code.
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A short one-line action-based description, e.g. 'Tests if a function is valid'
|
||||
.DESCRIPTION
|
||||
A longer description of the function, its purpose, common use cases, etc.
|
||||
.NOTES
|
||||
Information or caveats about the function e.g. 'This function is not supported in Linux'
|
||||
.LINK
|
||||
Specify a URI to a help page, this will show when Get-Help -Online is used.
|
||||
.EXAMPLE
|
||||
Test-MyTestFunction -Verbose
|
||||
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
|
||||
#>
|
||||
|
||||
|
||||
|
||||
<# Scheduled Task metadata
|
||||
General
|
||||
Get IPs for PA
|
||||
run as ndgov\!itdvcenterppa
|
||||
run whether user is logged on or not
|
||||
Triggers
|
||||
Daily, 11am
|
||||
Daily, 11pm
|
||||
Actions
|
||||
old-C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noninteractive -file "C:\itdscript\GetIPs.ps1"
|
||||
new-"C:\Program Files\PowerShell\7\pwsh.exe" -noninteractive -file "F:\GetVMwareVMGuestIPsForPA\GetVMwareVMGuestIPsForPA.ps1"
|
||||
|
||||
Settings
|
||||
allow task to be run on demand
|
||||
stop the task if it runs longer than 1 hour -eq $true
|
||||
if the running task does not end when requested, force it to stop
|
||||
#>
|
||||
|
||||
$TimeStamp = Get-Date -UFormat "%Y%m%d%H%M%S"
|
||||
|
||||
#Connect
|
||||
Connect-ITDvCenter -Credential $Secret:ndgov_svcitdvmvcauto
|
||||
|
||||
|
||||
##Windows
|
||||
|
||||
|
||||
#Output File
|
||||
$OutFileWin = "c:\inetpub\wwwroot\Win.txt"
|
||||
#$Date = Get-Date -UFormat "%Y%m%d%H%M%S"
|
||||
Get-Item -Path $OutFileWin | Copy-Item -Destination "F:\GetVMwareVMGuestIPsForPA\Backup\Win\$Timestamp-Win.txt"
|
||||
Remove-Item $OutFileWin
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
#Get Powered On VM's
|
||||
$vmwin = get-VM | Where-Object { $_.PowerState -eq "PoweredOn" `
|
||||
-and ($_.GuestID -eq "windows7Guest" `
|
||||
-or $_.GuestID -eq "windows7_64Guest" `
|
||||
-or $_.GuestID -eq "windows7Server64Guest" `
|
||||
-or $_.GuestID -eq "windows8_64Guest" `
|
||||
-or $_.GuestID -eq "windows8Server64Guest" `
|
||||
-or $_.GuestID -eq "windows9Server64Guest" `
|
||||
-or $_.GuestID -eq "winLonghorn64Guest" `
|
||||
-or $_.GuestID -eq "winLonghornGuest" `
|
||||
-or $_.GuestID -eq "winNetStandardGuest" `
|
||||
-or $_.GuestID -eq "winNetEnterpriseGuest" `
|
||||
-or $_.GuestID -eq "windows9_64Guest" `
|
||||
-or $_.GuestID -eq "windows2019srv_64Guest" `
|
||||
-or $_.GuestID -eq "windows2019srvNext_64Guest") }
|
||||
$vmviewwin = $vmwin | Get-View
|
||||
|
||||
$Outputwin = ""
|
||||
|
||||
#Loop through VM's, NIC's, and IP addresses.
|
||||
Foreach ($v in $vmviewwin) {
|
||||
Foreach ($nic in $v.Guest.Net) {
|
||||
Foreach ($IP in $nic.IPAddress) {
|
||||
If ($IP -notlike "fe80*" -and $IP -notlike "192.168.*" -and $IP -notlike "172.16*") {
|
||||
$OutputWin += $IP + "`n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#If ($Outputwin -ne "") {$OutputWin | Out-File $OutFileWin -Encoding utf8 -NoNewline}
|
||||
If ($Outputwin -ne "") { $OutputWin | Out-File $OutFileWin -Encoding ASCII -NoNewline }
|
||||
|
||||
|
||||
|
||||
##Linux
|
||||
|
||||
#Output File
|
||||
$OutFileLin = "c:\inetpub\wwwroot\Lin.txt"
|
||||
$Date = Get-Date -UFormat "%Y%m%d%H%M%S"
|
||||
Get-Item -Path $OutFileLin | Copy-Item -Destination "F:\GetVMwareVMGuestIPsForPA\Backup\Lin\$Timestamp-Lin.txt"
|
||||
Remove-Item $OutFileLin
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
#Get Powered On VM's
|
||||
$vmLin = get-VM | Where-Object { $_.PowerState -eq "PoweredOn" `
|
||||
-and ($_.GuestID -eq "centos6_64Guest" `
|
||||
-or $_.GuestID -eq "centos64Guest" `
|
||||
-or $_.GuestID -eq "centos7_64Guest" `
|
||||
-or $_.GuestID -eq "oracleLinux64Guest" `
|
||||
-or $_.GuestID -eq "oracleLinux7_64Guest" `
|
||||
-or $_.GuestID -eq "rhel4Guest" `
|
||||
-or $_.GuestID -eq "rhel5Guest" `
|
||||
-or $_.GuestID -eq "rhel5_64Guest" `
|
||||
-or $_.GuestID -eq "rhel6Guest" `
|
||||
-or $_.GuestID -eq "rhel6_64Guest" `
|
||||
-or $_.GuestID -eq "rhel7_64Guest" `
|
||||
-or $_.GuestID -eq "rhel8_64Guest" `
|
||||
-or $_.GuestID -eq "rhel9_64Guest" `
|
||||
-or $_.GuestID -eq "sles11_64Guest" `
|
||||
-or $_.GuestID -eq "sles12_64Guest" `
|
||||
-or $_.GuestID -eq "ubuntu64Guest") }
|
||||
$vmviewlin = $vmLin | Get-View
|
||||
|
||||
$OutputLin = ""
|
||||
|
||||
#Loop through VM's, NIC's, and IP addresses.
|
||||
Foreach ($v in $vmviewlin) {
|
||||
Foreach ($nic in $v.Guest.Net) {
|
||||
Foreach ($IP in $nic.IPAddress) {
|
||||
If ($IP -notlike "fe80*" -and $IP -notlike "192.168.*" -and $IP -notlike "172.16*") {
|
||||
$OutputLin += $IP + "`n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#If ($OutputLin -ne "") {$OutputLin | Out-File $OutFileLin -Encoding utf8 -NoNewline}
|
||||
If ($OutputLin -ne "") { $OutputLin | Out-File $OutFileLin -Encoding ASCII -NoNewline }
|
||||
|
||||
Disconnect-ITDvCenter
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Daily HPE OneView enclosure and server inventory for PowerBI trending and lifecycle planning.
|
||||
|
||||
.DESCRIPTION
|
||||
Iterates across all configured HPE OneView appliances, collecting enclosure and server
|
||||
hardware metadata including compute capacity, power state, and server profile assignment.
|
||||
Exports timestamped CSVs and inserts into SQL each run.
|
||||
Append daily runs to build a historical dataset for PowerBI trend analysis and
|
||||
physical hardware purchasing / lifecycle decisions.
|
||||
|
||||
Appliances processed:
|
||||
itdoneviewp1.nd.gov - Rack servers only (no enclosures)
|
||||
itdbissyncompp1.nd.gov - Synergy enclosures + servers
|
||||
itdmdnsyncompp1.nd.gov - Synergy enclosures + servers
|
||||
|
||||
.NOTES
|
||||
Run the following DDL once to create the destination tables:
|
||||
|
||||
DROP TABLE IF EXISTS [dbo].[VMware_Trends_Enclosure]
|
||||
|
||||
CREATE TABLE [dbo].[VMware_Trends_Enclosure] (
|
||||
[ReportDate] DATETIME2 NOT NULL,
|
||||
[ApplianceConnection] NVARCHAR(100) NULL,
|
||||
[EnclosureName] NVARCHAR(255) NOT NULL,
|
||||
[EnclosureModel] NVARCHAR(255) NULL,
|
||||
[EnclosureSerialNumber] NVARCHAR(100) NULL,
|
||||
[Status] NVARCHAR(50) NULL,
|
||||
[DeviceBayCount] INT NULL
|
||||
)
|
||||
|
||||
DROP TABLE IF EXISTS [dbo].[VMware_Trends_Server]
|
||||
|
||||
CREATE TABLE [dbo].[VMware_Trends_Server] (
|
||||
[ReportDate] DATETIME2 NOT NULL,
|
||||
[ApplianceConnection] NVARCHAR(100) NULL,
|
||||
[EnclosureName] NVARCHAR(255) NULL,
|
||||
[BayNumber] INT NULL,
|
||||
[ServerHardwareName] NVARCHAR(255) NOT NULL,
|
||||
[ServerName] NVARCHAR(255) NULL,
|
||||
[ServerModel] NVARCHAR(255) NULL,
|
||||
[ServerSerialNumber] NVARCHAR(100) NULL,
|
||||
[Status] NVARCHAR(50) NULL,
|
||||
[PowerState] NVARCHAR(50) NULL,
|
||||
[ServerProfileName] NVARCHAR(255) NULL,
|
||||
[ProcessorType] NVARCHAR(255) NULL,
|
||||
[ProcessorCount] INT NULL,
|
||||
[MemoryGB] DECIMAL(10,2) NULL,
|
||||
[FormFactor] NVARCHAR(100) NULL
|
||||
)
|
||||
|
||||
EnclosureName and BayNumber are NULL for rack-mounted servers not seated in an enclosure.
|
||||
ServerName is the iLO/DNS hostname populated once a server profile is assigned.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
)
|
||||
|
||||
#region --- Setup ---------------------------------------------------------------
|
||||
[string[]] $OVHostnames = @(
|
||||
'itdoneviewp1.nd.gov',
|
||||
'itdbissyncompp1.nd.gov',
|
||||
'itdmdnsyncompp1.nd.gov'
|
||||
)
|
||||
[string] $OutputPath = 'C:\temp\OV_Trends\'
|
||||
[string] $ServerInstance = 'itdintsql22p1.nd.gov\INTSQL22P1'
|
||||
[string] $Database = 'ITD-Systems-Automation'
|
||||
[string] $EnclosureTable = 'VMware_Trends_Enclosure'
|
||||
[string] $ServerTable = 'VMware_Trends_Server'
|
||||
[System.Management.Automation.PSCredential] $OVCredential = $Secret:ndgov_svcitdvmhpe
|
||||
[System.Management.Automation.PSCredential] $SqlCredential = $Secret:sql_itdpsu1
|
||||
|
||||
$RunDate = Get-Date
|
||||
$DateStamp = $RunDate.ToString('yyyyMMdd')
|
||||
$Timestamp = $RunDate.ToString('yyyy-MM-dd')
|
||||
|
||||
if (-not (Test-Path -Path $OutputPath)) {
|
||||
New-Item -ItemType Directory -Path $OutputPath | Out-Null
|
||||
}
|
||||
|
||||
Start-Transcript -Path (Join-Path $OutputPath "OVServerInventory_$DateStamp.log") -Append
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Collect Data From All Appliances -----------------------------------
|
||||
|
||||
$AllEnclosureResults = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
$AllServerResults = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
foreach ($OVHostname in $OVHostnames) {
|
||||
|
||||
Write-Verbose "Connecting to HPE OneView: $OVHostname"
|
||||
Connect-OVMgmt -Hostname $OVHostname -Credential $OVCredential -AuthLoginDomain nd.gov -LoginAcknowledge
|
||||
|
||||
#--- Enclosures ------------------------------------------------------------
|
||||
|
||||
Write-Verbose "[$OVHostname] Gathering enclosures..."
|
||||
$Enclosures = Get-OVEnclosure
|
||||
|
||||
# Build URI map for server lookups (only populated if there are enclosures)
|
||||
$EnclosureUriMap = @{}
|
||||
foreach ($Enclosure in $Enclosures) {
|
||||
$EnclosureUriMap[$Enclosure.uri] = $Enclosure.name
|
||||
|
||||
$AllEnclosureResults.Add([PSCustomObject]@{
|
||||
ReportDate = $Timestamp
|
||||
ApplianceConnection = $OVHostname
|
||||
EnclosureName = $Enclosure.name
|
||||
EnclosureModel = $Enclosure.model
|
||||
EnclosureSerialNumber = $Enclosure.serialNumber
|
||||
Status = $Enclosure.status
|
||||
DeviceBayCount = ($Enclosure.deviceBays | Measure-Object).Count
|
||||
})
|
||||
}
|
||||
|
||||
#--- Servers ---------------------------------------------------------------
|
||||
|
||||
Write-Verbose "[$OVHostname] Gathering servers..."
|
||||
$Servers = Get-OVServer | Where-Object { $_.serverName -like 'itdvm*' }
|
||||
|
||||
# Build profile URI map for this appliance
|
||||
$ProfileUriMap = @{}
|
||||
Get-OVServerProfile | ForEach-Object {
|
||||
$ProfileUriMap[$_.uri] = $_.name
|
||||
}
|
||||
|
||||
Write-Verbose "[$OVHostname] Processing $($Servers.Count) servers..."
|
||||
|
||||
foreach ($Server in $Servers) {
|
||||
|
||||
$EnclosureName = if ($Server.locationUri) {
|
||||
$EnclosureUriMap[$Server.locationUri]
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
|
||||
$ProfileName = if ($Server.serverProfileUri) {
|
||||
$ProfileUriMap[$Server.serverProfileUri]
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
|
||||
$MemoryGB = if ($null -ne $Server.memoryMb -and $Server.memoryMb -gt 0) {
|
||||
[Math]::Round($Server.memoryMb / 1024, 2)
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
|
||||
$AllServerResults.Add([PSCustomObject]@{
|
||||
ReportDate = $Timestamp
|
||||
ApplianceConnection = $OVHostname
|
||||
EnclosureName = $EnclosureName
|
||||
BayNumber = if ($null -ne $Server.position) { [int]$Server.position } else { $null }
|
||||
ServerHardwareName = $Server.name
|
||||
ServerName = $Server.serverName
|
||||
ServerModel = $Server.model
|
||||
ServerSerialNumber = $Server.serialNumber
|
||||
Status = $Server.status
|
||||
PowerState = $Server.powerState
|
||||
ServerProfileName = $ProfileName
|
||||
ProcessorType = $Server.processorType
|
||||
ProcessorCount = if ($null -ne $Server.processorCount) { [int]$Server.processorCount } else { $null }
|
||||
MemoryGB = $MemoryGB
|
||||
FormFactor = $Server.formFactor
|
||||
})
|
||||
}
|
||||
|
||||
Disconnect-OVMgmt
|
||||
Write-Verbose "[$OVHostname] Disconnected."
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Export CSVs --------------------------------------------------------
|
||||
|
||||
$EnclosureCsv = Join-Path $OutputPath "OVEnclosures_$DateStamp.csv"
|
||||
$ServerCsv = Join-Path $OutputPath "OVServers_$DateStamp.csv"
|
||||
|
||||
$AllEnclosureResults | Export-Csv -Path $EnclosureCsv -NoTypeInformation
|
||||
$AllServerResults | Export-Csv -Path $ServerCsv -NoTypeInformation
|
||||
|
||||
Write-Verbose "Exported $($AllEnclosureResults.Count) enclosure records to: $EnclosureCsv"
|
||||
Write-Verbose "Exported $($AllServerResults.Count) server records to: $ServerCsv"
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- SQL Insert: Enclosures ---------------------------------------------
|
||||
|
||||
$EnclosureTable_DT = [System.Data.DataTable]::new()
|
||||
|
||||
$EnclosureColDefs = [ordered]@{
|
||||
ReportDate = [datetime]
|
||||
ApplianceConnection = [string]
|
||||
EnclosureName = [string]
|
||||
EnclosureModel = [string]
|
||||
EnclosureSerialNumber = [string]
|
||||
Status = [string]
|
||||
DeviceBayCount = [int]
|
||||
}
|
||||
|
||||
foreach ($Col in $EnclosureColDefs.GetEnumerator()) {
|
||||
$Column = [System.Data.DataColumn]::new($Col.Key, $Col.Value)
|
||||
$Column.AllowDBNull = $true
|
||||
[void]$EnclosureTable_DT.Columns.Add($Column)
|
||||
}
|
||||
|
||||
foreach ($Row in $AllEnclosureResults) {
|
||||
$DataRow = $EnclosureTable_DT.NewRow()
|
||||
foreach ($Col in $EnclosureColDefs.Keys) {
|
||||
$Val = $Row.$Col
|
||||
$DataRow[$Col] = if ($null -ne $Val) { $Val } else { [DBNull]::Value }
|
||||
}
|
||||
[void]$EnclosureTable_DT.Rows.Add($DataRow)
|
||||
}
|
||||
|
||||
Write-SqlTableData -ServerInstance $ServerInstance -DatabaseName $Database -SchemaName 'dbo' `
|
||||
-TableName $EnclosureTable -Credential $SqlCredential -InputData $EnclosureTable_DT
|
||||
|
||||
Write-Verbose "Inserted $($EnclosureTable_DT.Rows.Count) enclosure records into [$Database].[dbo].[$EnclosureTable]"
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- SQL Insert: Servers ------------------------------------------------
|
||||
|
||||
$ServerTable_DT = [System.Data.DataTable]::new()
|
||||
|
||||
$ServerColDefs = [ordered]@{
|
||||
ReportDate = [datetime]
|
||||
ApplianceConnection = [string]
|
||||
EnclosureName = [string]
|
||||
BayNumber = [int]
|
||||
ServerHardwareName = [string]
|
||||
ServerName = [string]
|
||||
ServerModel = [string]
|
||||
ServerSerialNumber = [string]
|
||||
Status = [string]
|
||||
PowerState = [string]
|
||||
ServerProfileName = [string]
|
||||
ProcessorType = [string]
|
||||
ProcessorCount = [int]
|
||||
MemoryGB = [decimal]
|
||||
FormFactor = [string]
|
||||
}
|
||||
|
||||
foreach ($Col in $ServerColDefs.GetEnumerator()) {
|
||||
$Column = [System.Data.DataColumn]::new($Col.Key, $Col.Value)
|
||||
$Column.AllowDBNull = $true
|
||||
[void]$ServerTable_DT.Columns.Add($Column)
|
||||
}
|
||||
|
||||
foreach ($Row in $AllServerResults) {
|
||||
$DataRow = $ServerTable_DT.NewRow()
|
||||
foreach ($Col in $ServerColDefs.Keys) {
|
||||
$Val = $Row.$Col
|
||||
$DataRow[$Col] = if ($null -ne $Val) { $Val } else { [DBNull]::Value }
|
||||
}
|
||||
[void]$ServerTable_DT.Rows.Add($DataRow)
|
||||
}
|
||||
|
||||
Write-SqlTableData -ServerInstance $ServerInstance -DatabaseName $Database -SchemaName 'dbo' `
|
||||
-TableName $ServerTable -Credential $SqlCredential -InputData $ServerTable_DT
|
||||
|
||||
Write-Verbose "Inserted $($ServerTable_DT.Rows.Count) server records into [$Database].[dbo].[$ServerTable]"
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Cleanup ------------------------------------------------------------
|
||||
Stop-Transcript
|
||||
|
||||
#endregion
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Daily VMware Cluster metadata report for PowerBI trending and capacity planning.
|
||||
|
||||
.DESCRIPTION
|
||||
Collects cluster-level metadata from vCenter including DRS/HA configuration,
|
||||
aggregate compute capacity, and effective resource availability.
|
||||
Exports a timestamped CSV and inserts into SQL each run.
|
||||
Append daily runs to build a historical dataset for PowerBI trend analysis.
|
||||
|
||||
.NOTES
|
||||
Run the following DDL once to create the destination table:
|
||||
|
||||
DROP TABLE IF EXISTS [dbo].[VMware_Trends_Cluster]
|
||||
|
||||
CREATE TABLE [dbo].[VMware_Trends_Cluster] (
|
||||
[ReportDate] DATETIME2 NOT NULL,
|
||||
[ClusterName] NVARCHAR(255) NOT NULL,
|
||||
[Datacenter] NVARCHAR(100) NULL,
|
||||
[DrsEnabled] BIT NOT NULL,
|
||||
[DrsMode] NVARCHAR(50) NULL,
|
||||
[HaEnabled] BIT NOT NULL,
|
||||
[HostCount] INT NULL,
|
||||
[EffectiveHostCount] INT NULL,
|
||||
[TotalCpuCores] INT NULL,
|
||||
[TotalCpuMhz] INT NULL,
|
||||
[EffectiveCpuMhz] INT NULL,
|
||||
[TotalMemoryGB] DECIMAL(10,2) NULL,
|
||||
[EffectiveMemoryGB] DECIMAL(10,2) NULL,
|
||||
[UsedMemoryGB] DECIMAL(10,2) NULL,
|
||||
[VMCount] INT NULL
|
||||
)
|
||||
|
||||
TotalCpuMhz, EffectiveCpuMhz, TotalMemoryGB, and EffectiveMemoryGB come from
|
||||
vCenter's cluster summary (real-time aggregate).
|
||||
UsedMemoryGB is summed from each host's current MemoryUsageGB.
|
||||
TotalCpuCores is summed from each host's hardware CpuInfo.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
)
|
||||
|
||||
Connect-ITDvCenter -Credential $Secret:ndgov_svcitdvmvcro
|
||||
|
||||
#region --- Setup ---------------------------------------------------------------
|
||||
[string] $OutputPath = 'C:\temp\VM_Trends\'
|
||||
[string] $ServerInstance = 'itdintsql22p1.nd.gov\INTSQL22P1'
|
||||
[string] $Database = 'ITD-Systems-Automation'
|
||||
[string] $Table = 'VMware_Trends_Cluster'
|
||||
[System.Management.Automation.PSCredential] $SqlCredential = $Secret:sql_itdpsu1
|
||||
|
||||
$RunDate = Get-Date
|
||||
$DateStamp = $RunDate.ToString('yyyyMMdd')
|
||||
$Timestamp = $RunDate.ToString('yyyy-MM-dd')
|
||||
|
||||
if (-not (Test-Path -Path $OutputPath)) {
|
||||
New-Item -ItemType Directory -Path $OutputPath | Out-Null
|
||||
}
|
||||
|
||||
Start-Transcript -Path (Join-Path $OutputPath "ClusterMetadataReport_$DateStamp.log") -Append
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Build Cluster -> Datacenter Lookup ---------------------------------
|
||||
|
||||
Write-Verbose 'Building cluster-to-datacenter map...'
|
||||
$ClusterDatacenterMap = @{}
|
||||
|
||||
Get-Datacenter | ForEach-Object {
|
||||
$DatacenterName = $_.Name
|
||||
Get-Cluster -Location $_ | ForEach-Object {
|
||||
$ClusterDatacenterMap[$_.Name] = $DatacenterName
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Collect Cluster Data -----------------------------------------------
|
||||
|
||||
Write-Verbose 'Gathering clusters...'
|
||||
$AllClusters = Get-Cluster
|
||||
|
||||
Write-Verbose "Processing $($AllClusters.Count) clusters..."
|
||||
|
||||
$Results = foreach ($Cluster in $AllClusters) {
|
||||
|
||||
$Summary = $Cluster.ExtensionData.Summary # reuse for all summary fields
|
||||
|
||||
#--- Aggregate host-level stats that vCenter summary does not expose
|
||||
$ClusterHosts = Get-VMHost -Location $Cluster
|
||||
$TotalCpuCores = ($ClusterHosts | ForEach-Object {
|
||||
$_.ExtensionData.Hardware.CpuInfo.NumCpuCores
|
||||
} | Measure-Object -Sum).Sum
|
||||
$UsedMemoryGB = [Math]::Round(
|
||||
($ClusterHosts | Measure-Object -Property MemoryUsageGB -Sum).Sum, 2
|
||||
)
|
||||
$VMCount = ($ClusterHosts | ForEach-Object {
|
||||
($_.ExtensionData.Vm | Measure-Object).Count
|
||||
} | Measure-Object -Sum).Sum
|
||||
|
||||
#--- Capacity from cluster summary
|
||||
$TotalCpuMhz = $Summary.TotalCpu # MHz
|
||||
$EffectiveCpuMhz = $Summary.EffectiveCpu # MHz
|
||||
$TotalMemoryGB = [Math]::Round($Summary.TotalMemory / 1GB, 2) # bytes -> GB
|
||||
$EffectiveMemGB = [Math]::Round($Summary.EffectiveMemory / 1KB, 2) # MB -> GB
|
||||
|
||||
[PSCustomObject]@{
|
||||
ReportDate = $Timestamp
|
||||
ClusterName = $Cluster.Name
|
||||
Datacenter = $ClusterDatacenterMap[$Cluster.Name]
|
||||
DrsEnabled = $Cluster.DrsEnabled
|
||||
DrsMode = $Cluster.DrsAutomationLevel
|
||||
HaEnabled = $Cluster.HAEnabled
|
||||
HostCount = $Summary.NumHosts
|
||||
EffectiveHostCount = $Summary.NumEffectiveHosts
|
||||
TotalCpuCores = [int]$TotalCpuCores
|
||||
TotalCpuMhz = $TotalCpuMhz
|
||||
EffectiveCpuMhz = $EffectiveCpuMhz
|
||||
TotalMemoryGB = $TotalMemoryGB
|
||||
EffectiveMemoryGB = $EffectiveMemGB
|
||||
UsedMemoryGB = $UsedMemoryGB
|
||||
VMCount = [int]$VMCount
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Export CSV ---------------------------------------------------------
|
||||
|
||||
$OutputFile = Join-Path $OutputPath "ClusterMetadata_$DateStamp.csv"
|
||||
$Results | Export-Csv -Path $OutputFile -NoTypeInformation
|
||||
Write-Verbose "Exported $($Results.Count) cluster records to: $OutputFile"
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- SQL Insert ---------------------------------------------------------
|
||||
|
||||
$DataTable = [System.Data.DataTable]::new()
|
||||
|
||||
$ColDefs = [ordered]@{
|
||||
ReportDate = [datetime]
|
||||
ClusterName = [string]
|
||||
Datacenter = [string]
|
||||
DrsEnabled = [bool]
|
||||
DrsMode = [string]
|
||||
HaEnabled = [bool]
|
||||
HostCount = [int]
|
||||
EffectiveHostCount = [int]
|
||||
TotalCpuCores = [int]
|
||||
TotalCpuMhz = [int]
|
||||
EffectiveCpuMhz = [int]
|
||||
TotalMemoryGB = [decimal]
|
||||
EffectiveMemoryGB = [decimal]
|
||||
UsedMemoryGB = [decimal]
|
||||
VMCount = [int]
|
||||
}
|
||||
|
||||
foreach ($Col in $ColDefs.GetEnumerator()) {
|
||||
$Column = [System.Data.DataColumn]::new($Col.Key, $Col.Value)
|
||||
$Column.AllowDBNull = $true
|
||||
[void]$DataTable.Columns.Add($Column)
|
||||
}
|
||||
|
||||
foreach ($Row in $Results) {
|
||||
$DataRow = $DataTable.NewRow()
|
||||
foreach ($Col in $ColDefs.Keys) {
|
||||
$Val = $Row.$Col
|
||||
$DataRow[$Col] = if ($null -ne $Val) { $Val } else { [DBNull]::Value }
|
||||
}
|
||||
[void]$DataTable.Rows.Add($DataRow)
|
||||
}
|
||||
|
||||
$SqlParams = @{
|
||||
ServerInstance = $ServerInstance
|
||||
DatabaseName = $Database
|
||||
SchemaName = 'dbo'
|
||||
TableName = $Table
|
||||
Credential = $SqlCredential
|
||||
InputData = $DataTable
|
||||
}
|
||||
Write-SqlTableData @SqlParams
|
||||
|
||||
Write-Verbose "Inserted $($DataTable.Rows.Count) cluster records into [$Database].[dbo].[$Table]"
|
||||
|
||||
#endregion
|
||||
|
||||
Disconnect-ITDvCenter
|
||||
|
||||
#region --- Cleanup -------------------------------------------------------------
|
||||
Stop-Transcript
|
||||
|
||||
#endregion
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Daily VMware Host metadata report for PowerBI trending and hardware capacity planning.
|
||||
|
||||
.DESCRIPTION
|
||||
Collects ESXi host metadata from vCenter including hardware, compute capacity, and
|
||||
version information. Exports a timestamped CSV and inserts into SQL each run.
|
||||
Append daily runs to build a historical dataset for PowerBI trend analysis and
|
||||
physical hardware purchasing / lifecycle decisions.
|
||||
|
||||
.NOTES
|
||||
Run the following DDL once to create the destination table:
|
||||
|
||||
DROP TABLE IF EXISTS [dbo].[VMware_Trends_Host]
|
||||
|
||||
CREATE TABLE [dbo].[VMware_Trends_Host] (
|
||||
[ReportDate] DATETIME2 NOT NULL,
|
||||
[HostName] NVARCHAR(255) NOT NULL,
|
||||
[Datacenter] NVARCHAR(100) NULL,
|
||||
[Cluster] NVARCHAR(100) NULL,
|
||||
[ConnectionState] NVARCHAR(50) NULL,
|
||||
[PowerState] NVARCHAR(50) NULL,
|
||||
[InMaintenanceMode] BIT NOT NULL,
|
||||
[Model] NVARCHAR(255) NULL,
|
||||
[ProcessorType] NVARCHAR(255) NULL,
|
||||
[CpuSockets] INT NULL,
|
||||
[CoresPerSocket] INT NULL,
|
||||
[TotalCpuCores] INT NULL,
|
||||
[CpuMhz] INT NULL,
|
||||
[TotalCpuMhz] INT NULL,
|
||||
[MemoryTotalGB] DECIMAL(10,2) NULL,
|
||||
[MemoryUsageGB] DECIMAL(10,2) NULL,
|
||||
[VMCount] INT NULL,
|
||||
[EsxiVersion] NVARCHAR(50) NULL,
|
||||
[EsxiBuild] NVARCHAR(50) NULL,
|
||||
[UptimeDays] DECIMAL(10,2) NULL
|
||||
)
|
||||
|
||||
MemoryUsageGB reflects real-time memory consumed by running VMs at the time of collection.
|
||||
UptimeDays will be NULL for hosts that are powered off or disconnected.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
)
|
||||
|
||||
Connect-ITDvCenter -Credential $Secret:ndgov_svcitdvmvcro
|
||||
|
||||
#region --- Setup ---------------------------------------------------------------
|
||||
[string] $OutputPath = 'C:\temp\VM_Trends\'
|
||||
[string] $ServerInstance = 'itdintsql22p1.nd.gov\INTSQL22P1'
|
||||
[string] $Database = 'ITD-Systems-Automation'
|
||||
[string] $Table = 'VMware_Trends_Host'
|
||||
[System.Management.Automation.PSCredential] $SqlCredential = $Secret:sql_itdpsu1
|
||||
|
||||
$RunDate = Get-Date
|
||||
$DateStamp = $RunDate.ToString('yyyyMMdd')
|
||||
$Timestamp = $RunDate.ToString('yyyy-MM-dd')
|
||||
|
||||
if (-not (Test-Path -Path $OutputPath)) {
|
||||
New-Item -ItemType Directory -Path $OutputPath | Out-Null
|
||||
}
|
||||
|
||||
Start-Transcript -Path (Join-Path $OutputPath "VMHostMetadataReport_$DateStamp.log") -Append
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Build VMHost -> Cluster/Datacenter Lookups (avoids per-host queries) -
|
||||
|
||||
Write-Verbose 'Building host-to-cluster and host-to-datacenter maps...'
|
||||
$HostClusterMap = @{}
|
||||
$HostDatacenterMap = @{}
|
||||
|
||||
Get-Datacenter | ForEach-Object {
|
||||
$DatacenterName = $_.Name
|
||||
Get-VMHost -Location $_ | ForEach-Object {
|
||||
$HostDatacenterMap[$_.Name] = $DatacenterName
|
||||
}
|
||||
}
|
||||
|
||||
Get-Cluster | ForEach-Object {
|
||||
$ClusterName = $_.Name
|
||||
Get-VMHost -Location $_ | ForEach-Object {
|
||||
$HostClusterMap[$_.Name] = $ClusterName
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Collect Host Data --------------------------------------------------
|
||||
|
||||
Write-Verbose 'Gathering hosts...'
|
||||
$AllHosts = $HostDatacenterMap.Keys | ForEach-Object { Get-VMHost -Name $_ }
|
||||
|
||||
Write-Verbose "Processing $($AllHosts.Count) hosts..."
|
||||
|
||||
$Results = foreach ($VMHost in $AllHosts) {
|
||||
|
||||
$Ext = $VMHost.ExtensionData # single API object -- reuse for all fields
|
||||
|
||||
#--- CPU info from hardware summary
|
||||
$CpuSockets = $Ext.Hardware.CpuInfo.NumCpuPackages
|
||||
$TotalCpuCores = $Ext.Hardware.CpuInfo.NumCpuCores
|
||||
$CoresPerSocket = if ($CpuSockets -gt 0) { [int]($TotalCpuCores / $CpuSockets) } else { $null }
|
||||
$CpuMhz = $Ext.Hardware.CpuInfo.Hz / 1000000 # Hz -> MHz
|
||||
$TotalCpuMhz = $Ext.Summary.Hardware.CpuMhz * $TotalCpuCores
|
||||
|
||||
#--- Memory
|
||||
$MemoryTotalGB = [Math]::Round($VMHost.MemoryTotalGB, 2)
|
||||
$MemoryUsageGB = [Math]::Round($VMHost.MemoryUsageGB, 2)
|
||||
|
||||
#--- VM count on this host right now
|
||||
$VMCount = ($Ext.Vm | Measure-Object).Count
|
||||
|
||||
#--- Uptime (null when host is powered off / disconnected)
|
||||
$UptimeDays = $null
|
||||
$BootTime = $Ext.Runtime.BootTime
|
||||
if ($null -ne $BootTime) {
|
||||
$UptimeDays = [Math]::Round(($RunDate - $BootTime).TotalDays, 2)
|
||||
}
|
||||
|
||||
[PSCustomObject]@{
|
||||
ReportDate = $Timestamp
|
||||
HostName = $VMHost.Name
|
||||
Datacenter = $HostDatacenterMap[$VMHost.Name]
|
||||
Cluster = $HostClusterMap[$VMHost.Name]
|
||||
ConnectionState = $VMHost.ConnectionState
|
||||
PowerState = $VMHost.PowerState
|
||||
InMaintenanceMode = $VMHost.ExtensionData.Runtime.InMaintenanceMode
|
||||
Model = $Ext.Hardware.SystemInfo.Model
|
||||
ProcessorType = $VMHost.ProcessorType
|
||||
CpuSockets = $CpuSockets
|
||||
CoresPerSocket = $CoresPerSocket
|
||||
TotalCpuCores = $TotalCpuCores
|
||||
CpuMhz = [int]$CpuMhz
|
||||
TotalCpuMhz = $TotalCpuMhz
|
||||
MemoryTotalGB = $MemoryTotalGB
|
||||
MemoryUsageGB = $MemoryUsageGB
|
||||
VMCount = $VMCount
|
||||
EsxiVersion = $VMHost.Version
|
||||
EsxiBuild = $VMHost.Build
|
||||
UptimeDays = $UptimeDays
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Export CSV ---------------------------------------------------------
|
||||
|
||||
$OutputFile = Join-Path $OutputPath "VMHostMetadata_$DateStamp.csv"
|
||||
$Results | Export-Csv -Path $OutputFile -NoTypeInformation
|
||||
Write-Verbose "Exported $($Results.Count) host records to: $OutputFile"
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- SQL Insert ---------------------------------------------------------
|
||||
|
||||
$DataTable = [System.Data.DataTable]::new()
|
||||
|
||||
$ColDefs = [ordered]@{
|
||||
ReportDate = [datetime]
|
||||
HostName = [string]
|
||||
Datacenter = [string]
|
||||
Cluster = [string]
|
||||
ConnectionState = [string]
|
||||
PowerState = [string]
|
||||
InMaintenanceMode = [bool]
|
||||
Model = [string]
|
||||
ProcessorType = [string]
|
||||
CpuSockets = [int]
|
||||
CoresPerSocket = [int]
|
||||
TotalCpuCores = [int]
|
||||
CpuMhz = [int]
|
||||
TotalCpuMhz = [int]
|
||||
MemoryTotalGB = [decimal]
|
||||
MemoryUsageGB = [decimal]
|
||||
VMCount = [int]
|
||||
EsxiVersion = [string]
|
||||
EsxiBuild = [string]
|
||||
UptimeDays = [decimal]
|
||||
}
|
||||
|
||||
foreach ($Col in $ColDefs.GetEnumerator()) {
|
||||
$Column = [System.Data.DataColumn]::new($Col.Key, $Col.Value)
|
||||
$Column.AllowDBNull = $true
|
||||
[void]$DataTable.Columns.Add($Column)
|
||||
}
|
||||
|
||||
foreach ($Row in $Results) {
|
||||
$DataRow = $DataTable.NewRow()
|
||||
foreach ($Col in $ColDefs.Keys) {
|
||||
$Val = $Row.$Col
|
||||
$DataRow[$Col] = if ($null -ne $Val) { $Val } else { [DBNull]::Value }
|
||||
}
|
||||
[void]$DataTable.Rows.Add($DataRow)
|
||||
}
|
||||
|
||||
$SqlParams = @{
|
||||
ServerInstance = $ServerInstance
|
||||
DatabaseName = $Database
|
||||
SchemaName = 'dbo'
|
||||
TableName = $Table
|
||||
Credential = $SqlCredential
|
||||
InputData = $DataTable
|
||||
}
|
||||
Write-SqlTableData @SqlParams
|
||||
|
||||
Write-Verbose "Inserted $($DataTable.Rows.Count) host records into [$Database].[dbo].[$Table]"
|
||||
|
||||
Disconnect-ITDvCenter
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Cleanup -------------------------------------------------------------
|
||||
Stop-Transcript
|
||||
|
||||
#endregion
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Daily VM metadata report for PowerBI trending and hardware capacity planning.
|
||||
|
||||
.DESCRIPTION
|
||||
Collects VM metadata from vCenter including compute, storage, OS, and VMware Tools
|
||||
information using only data available within vCenter (no direct guest connections).
|
||||
Exports a timestamped CSV each run -- append daily runs to build a historical dataset
|
||||
suitable for PowerBI trend analysis and physical hardware purchasing decisions.
|
||||
|
||||
.PARAMETER vCenterServers
|
||||
One or more vCenter server hostnames. Defaults to itdvmvc1.nd.gov and itdvmvc2.nd.gov.
|
||||
|
||||
.PARAMETER DatacenterFilter
|
||||
Wildcard filter applied to datacenter names. Defaults to 'Primary*'.
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Directory where CSV and log files are written.
|
||||
Defaults to C:\ITDSCRIPT\Reports\VMMetadata.
|
||||
|
||||
.PARAMETER CredentialPath
|
||||
Path to a saved PSCredential XML file for unattended/scheduled runs.
|
||||
Create one interactively with:
|
||||
Get-Credential | Export-Clixml -Path C:\ITDSCRIPT\Creds\vCenter.xml
|
||||
When omitted the script prompts for credentials.
|
||||
|
||||
.EXAMPLE
|
||||
# Interactive run
|
||||
.\VMware-VMDailyMetadataReport.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Scheduled / unattended run
|
||||
.\VMware-VMDailyMetadataReport.ps1 -CredentialPath 'C:\ITDSCRIPT\Creds\vCenter.xml'
|
||||
|
||||
.NOTES
|
||||
Run the following DDL once to create the destination table:
|
||||
|
||||
DROP TABLE IF EXISTS [dbo].[VMware_Trends_VM]
|
||||
|
||||
CREATE TABLE [dbo].[VMware_Trends_VM] (
|
||||
[ReportDate] DATETIME2 NOT NULL,
|
||||
[VMName] NVARCHAR(255) NOT NULL,
|
||||
[Datacenter] NVARCHAR(100) NULL,
|
||||
[Cluster] NVARCHAR(100) NULL,
|
||||
[PowerState] NVARCHAR(50) NULL,
|
||||
[IsSRMPlaceholder] BIT NOT NULL,
|
||||
[StoragePlatform] NVARCHAR(100) NULL,
|
||||
[GuestOS] NVARCHAR(255) NULL,
|
||||
[vCPUs] INT NULL,
|
||||
[MemoryGB] DECIMAL(10,2) NULL,
|
||||
[ProvisionedSpaceGB] DECIMAL(12,2) NULL,
|
||||
[UsedSpaceGB] DECIMAL(12,2) NULL,
|
||||
[GuestDiskCapacityGB] DECIMAL(12,2) NULL,
|
||||
[GuestDiskUsedGB] DECIMAL(12,2) NULL,
|
||||
[ToolsRunningStatus] NVARCHAR(100) NULL,
|
||||
[ToolsVersionStatus] NVARCHAR(100) NULL,
|
||||
[ToolsVersion] NVARCHAR(50) NULL,
|
||||
[Tag_DRProtection] NVARCHAR(100) NULL,
|
||||
[Tag_AppName] NVARCHAR(255) NULL,
|
||||
[Tag_VRDatastores] NVARCHAR(255) NULL,
|
||||
[Tag_VRRPO] NVARCHAR(100) NULL,
|
||||
[Tag_DTAP] NVARCHAR(50) NULL,
|
||||
[Tag_StartupPriority] NVARCHAR(100) NULL,
|
||||
[Tag_SRMRecoveryType] NVARCHAR(100) NULL,
|
||||
[Tag_LicensingRestrictions] NVARCHAR(255) NULL
|
||||
)
|
||||
|
||||
Guest OS disk capacity / used columns are NULL for powered-off VMs and SRM placeholders.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
)
|
||||
|
||||
Connect-ITDvCenter -Credential $Secret:ndgov_svcitdvmvcro
|
||||
|
||||
#region --- Setup ---------------------------------------------------------------
|
||||
[string] $OutputPath = 'C:\temp\VM_Trends\'
|
||||
[string] $ServerInstance = 'itdintsql22p1.nd.gov\INTSQL22P1'
|
||||
[string] $Database = 'ITD-Systems-Automation'
|
||||
[string] $Table = 'VMware_Trends_VM'
|
||||
[System.Management.Automation.PSCredential] $SqlCredential = $Secret:sql_itdpsu1
|
||||
|
||||
$RunDate = Get-Date
|
||||
$DateStamp = $RunDate.ToString('yyyyMMdd')
|
||||
$Timestamp = $RunDate.ToString('yyyy-MM-dd')
|
||||
|
||||
if (-not (Test-Path -Path $OutputPath)) {
|
||||
New-Item -ItemType Directory -Path $OutputPath | Out-Null
|
||||
}
|
||||
|
||||
Start-Transcript -Path (Join-Path $OutputPath "VMMetadataReport_$DateStamp.log") -Append
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Build VMHost -> Cluster/Datacenter Lookups (avoids per-VM API calls) -
|
||||
|
||||
Write-Verbose 'Building host-to-cluster and host-to-datacenter maps...'
|
||||
$HostClusterMap = @{}
|
||||
$HostDatacenterMap = @{}
|
||||
|
||||
Get-Datacenter | ForEach-Object {
|
||||
$DatacenterName = $_.Name
|
||||
Get-VMHost -Location $_ | ForEach-Object {
|
||||
$HostDatacenterMap[$_.Name] = $DatacenterName
|
||||
}
|
||||
}
|
||||
|
||||
Get-Cluster | ForEach-Object {
|
||||
$ClusterName = $_.Name
|
||||
Get-VMHost -Location $_ | ForEach-Object {
|
||||
$HostClusterMap[$_.Name] = $ClusterName
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Collect VM Data -----------------------------------------------------
|
||||
|
||||
Write-Verbose "Gathering VMs"
|
||||
|
||||
# Include ALL VMs (SRM placeholders flagged via column, not excluded).
|
||||
# vCLS agent VMs are excluded -- they are vSphere internal and not customer workloads.
|
||||
$AllVMs = Get-VM | Where-Object { $_.Name -notlike 'vCLS*' }
|
||||
|
||||
#--- Pre-fetch all tag assignments in one API call (avoids per-VM Get-TagAssignment)
|
||||
Write-Verbose 'Pre-fetching VM tag assignments...'
|
||||
$TagLookup = @{}
|
||||
Get-TagAssignment -Entity $AllVMs | ForEach-Object {
|
||||
$VMId = $_.Entity.Id
|
||||
$Cat = $_.Tag.Category.Name
|
||||
$TagName = $_.Tag.Name
|
||||
if (-not $TagLookup.ContainsKey($VMId)) { $TagLookup[$VMId] = @{} }
|
||||
if ($TagLookup[$VMId].ContainsKey($Cat)) {
|
||||
$TagLookup[$VMId][$Cat] += "; $TagName"
|
||||
} else {
|
||||
$TagLookup[$VMId][$Cat] = $TagName
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Processing $($AllVMs.Count) VMs..."
|
||||
|
||||
$Results = foreach ($VM in $AllVMs) {
|
||||
|
||||
$Ext = $VM.ExtensionData # single API object -- reuse for all fields
|
||||
|
||||
#--- SRM placeholder detection
|
||||
$IsSRMPlaceholder = $Ext.Summary.Config.ManagedBy.Type -eq 'placeholderVm'
|
||||
|
||||
#--- Cluster / Datacenter (null-safe: standalone hosts have no cluster entry)
|
||||
$ClusterName = $HostClusterMap[$VM.VMHost.Name]
|
||||
$DatacenterName = $HostDatacenterMap[$VM.VMHost.Name]
|
||||
|
||||
#--- Tag assignments (pre-fetched; null when category not assigned to this VM)
|
||||
$VMTags = if ($TagLookup.ContainsKey($VM.Id)) { $TagLookup[$VM.Id] } else { @{} }
|
||||
|
||||
#--- Storage platform parsed from datastore name convention: VMCLUSTER_LUN_PLATFORM_Desc
|
||||
# Segment 2 = storage platform identifier (e.g. FS92, A9K).
|
||||
# Cluster grouping uses the compute Cluster column -- no need to re-derive it here.
|
||||
$StoragePlatforms = foreach ($DSName in $Ext.Config.DatastoreUrl.Name) {
|
||||
$Segments = $DSName -split '_'
|
||||
if ($Segments.Count -ge 3) { $Segments[2] }
|
||||
}
|
||||
$StoragePlatform = ($StoragePlatforms | Sort-Object -Unique) -join '; '
|
||||
|
||||
#--- VMware Tools guest disk info
|
||||
# Populated only when Tools is running; null otherwise.
|
||||
$GuestDiskCapacityGB = $null
|
||||
$GuestDiskUsedGB = $null
|
||||
if ($Ext.Guest.Disk) {
|
||||
$TotalCapBytes = ($Ext.Guest.Disk | Measure-Object -Property Capacity -Sum).Sum
|
||||
$TotalFreeBytes = ($Ext.Guest.Disk | Measure-Object -Property FreeSpace -Sum).Sum
|
||||
$GuestDiskCapacityGB = [Math]::Round($TotalCapBytes / 1GB, 2)
|
||||
$GuestDiskUsedGB = [Math]::Round(($TotalCapBytes - $TotalFreeBytes) / 1GB, 2)
|
||||
}
|
||||
|
||||
[PSCustomObject]@{
|
||||
# --- Identity & grouping
|
||||
ReportDate = $Timestamp # for PowerBI time-series/trend axis
|
||||
VMName = $VM.Name
|
||||
Datacenter = $DatacenterName
|
||||
Cluster = $ClusterName
|
||||
PowerState = $VM.PowerState
|
||||
IsSRMPlaceholder = $IsSRMPlaceholder
|
||||
StoragePlatform = $StoragePlatform
|
||||
GuestOS = $Ext.Guest.GuestFullName
|
||||
|
||||
# --- Compute
|
||||
vCPUs = $VM.NumCpu
|
||||
MemoryGB = $VM.MemoryGB
|
||||
|
||||
# --- Datastore-level storage
|
||||
# ProvisionedSpaceGB : maximum the VM could consume (thin disks counted at max size)
|
||||
# UsedSpaceGB : bytes actually committed on datastores right now
|
||||
ProvisionedSpaceGB = [Math]::Round($VM.ProvisionedSpaceGB, 2)
|
||||
UsedSpaceGB = [Math]::Round($VM.UsedSpaceGB, 2)
|
||||
|
||||
# --- Guest OS-level storage (from VMware Tools; null when Tools not running)
|
||||
# GuestDiskCapacityGB : sum of all volume capacities seen inside the guest
|
||||
# GuestDiskUsedGB : sum of space consumed across those volumes
|
||||
GuestDiskCapacityGB = $GuestDiskCapacityGB
|
||||
GuestDiskUsedGB = $GuestDiskUsedGB
|
||||
|
||||
# --- VMware Tools
|
||||
# ToolsRunningStatus : guestToolsRunning | guestToolsNotRunning | guestToolsExecutingScripts
|
||||
# ToolsVersionStatus : guestToolsCurrent | guestToolsNeedUpgrade | guestToolsUnmanaged | guestToolsTooNew
|
||||
# ToolsVersion : numeric build version string reported by vCenter
|
||||
ToolsRunningStatus = $Ext.Guest.ToolsRunningStatus
|
||||
ToolsVersionStatus = $Ext.Guest.ToolsVersionStatus
|
||||
ToolsVersion = $Ext.Guest.ToolsVersion
|
||||
|
||||
# --- vCenter Tags
|
||||
Tag_DRProtection = $VMTags['DR Protection']
|
||||
Tag_AppName = $VMTags['AppName']
|
||||
Tag_VRDatastores = $VMTags['VR Datastores']
|
||||
Tag_VRRPO = $VMTags['VR RPO']
|
||||
Tag_DTAP = $VMTags['DTAP']
|
||||
Tag_StartupPriority = $VMTags['StartupPriority']
|
||||
Tag_SRMRecoveryType = $VMTags['SRM Recovery Type']
|
||||
Tag_LicensingRestrictions = $VMTags['LicensingRestrictions']
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
<#region --- Export CSV ---------------------------------------------------------
|
||||
|
||||
$OutputFile = Join-Path $OutputPath "VMMetadata_$DateStamp.csv"
|
||||
$Results | Export-Csv -Path $OutputFile -NoTypeInformation
|
||||
Write-Verbose "Exported $($Results.Count) VM records to: $OutputFile"
|
||||
|
||||
#endregion
|
||||
#>
|
||||
#region --- SQL Insert ---------------------------------------------------------
|
||||
|
||||
# Build a typed DataTable so Write-SqlTableData knows each column's type
|
||||
# even when nullable columns contain null values. Type inference from raw
|
||||
# PSCustomObjects fails when the first row has a null in a numeric column.
|
||||
$DataTable = [System.Data.DataTable]::new()
|
||||
|
||||
$ColDefs = [ordered]@{
|
||||
ReportDate = [datetime]
|
||||
VMName = [string]
|
||||
Datacenter = [string]
|
||||
Cluster = [string]
|
||||
PowerState = [string]
|
||||
IsSRMPlaceholder = [bool]
|
||||
StoragePlatform = [string]
|
||||
GuestOS = [string]
|
||||
vCPUs = [int]
|
||||
MemoryGB = [decimal]
|
||||
ProvisionedSpaceGB = [decimal]
|
||||
UsedSpaceGB = [decimal]
|
||||
GuestDiskCapacityGB = [decimal]
|
||||
GuestDiskUsedGB = [decimal]
|
||||
ToolsRunningStatus = [string]
|
||||
ToolsVersionStatus = [string]
|
||||
ToolsVersion = [string]
|
||||
Tag_DRProtection = [string]
|
||||
Tag_AppName = [string]
|
||||
Tag_VRDatastores = [string]
|
||||
Tag_VRRPO = [string]
|
||||
Tag_DTAP = [string]
|
||||
Tag_StartupPriority = [string]
|
||||
Tag_SRMRecoveryType = [string]
|
||||
Tag_LicensingRestrictions = [string]
|
||||
}
|
||||
|
||||
foreach ($Col in $ColDefs.GetEnumerator()) {
|
||||
$Column = [System.Data.DataColumn]::new($Col.Key, $Col.Value)
|
||||
$Column.AllowDBNull = $true
|
||||
[void]$DataTable.Columns.Add($Column)
|
||||
}
|
||||
|
||||
foreach ($Row in $Results) {
|
||||
$DataRow = $DataTable.NewRow()
|
||||
foreach ($Col in $ColDefs.Keys) {
|
||||
$Val = $Row.$Col
|
||||
$DataRow[$Col] = if ($null -ne $Val) { $Val } else { [DBNull]::Value }
|
||||
}
|
||||
[void]$DataTable.Rows.Add($DataRow)
|
||||
}
|
||||
|
||||
$SqlParams = @{
|
||||
ServerInstance = $ServerInstance
|
||||
DatabaseName = $Database
|
||||
SchemaName = 'dbo'
|
||||
TableName = $Table
|
||||
Credential = $SqlCredential
|
||||
InputData = $DataTable
|
||||
}
|
||||
Write-SqlTableData @SqlParams
|
||||
|
||||
Write-Verbose "Inserted $($DataTable.Rows.Count) VM records into [$Database].[dbo].[$Table]"
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- Cleanup -------------------------------------------------------------
|
||||
Stop-Transcript
|
||||
|
||||
#endregion
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
<# NOT FUNCTIONAL YET - WORK IN PROGRESS
|
||||
.SYNOPSIS
|
||||
Update/renew any expired iLO certificates in the VMware/Synergy environment
|
||||
.DESCRIPTION
|
||||
Update/renew any expired iLO certificates in the VMware/Synergy environment
|
||||
.NOTES
|
||||
# retrieve all iLO from Synergy
|
||||
# find all certificates expiring in the next 3 days, or ones that do not have nd.gov in name, add to an array
|
||||
# loop through list
|
||||
## connect to ilo
|
||||
## generate CSR
|
||||
## send CSR to sectigo to generate new
|
||||
## wait for approval
|
||||
## download new cert
|
||||
## upload/update/set on iLO
|
||||
.LINK
|
||||
|
||||
.EXAMPLE
|
||||
Test-MyTestFunction -Verbose
|
||||
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(ParameterSetName = 'ByHostName')]
|
||||
[string[]]
|
||||
$HostName = $null,
|
||||
|
||||
[Parameter(ParameterSetName = 'SynergyDiscovery')]
|
||||
[switch]
|
||||
$SynergyDiscovery
|
||||
|
||||
)
|
||||
|
||||
function Get-SectigoToken {
|
||||
Write-Verbose -Message "Retrieving Sectigo API key for VMware" -Verbose
|
||||
$SectigoCred = Get-ITDPassword -Title "Sectigo API key for VMware" -UserName "f595aa76-c26b-4664-b95d-cb805cc7ff4e"
|
||||
Write-Verbose -Message "Confirming SectigoCred is $($SectigoCred.UserName)" -Verbose
|
||||
$AuthBody = @{
|
||||
grant_type = 'client_credentials'
|
||||
#client_id = $Secret:sectigo_vmware.UserName
|
||||
#client_secret = $Secret:sectigo_vmware.GetNetworkCredential().Password
|
||||
client_id = $SectigoCred.UserName
|
||||
client_secret = $SectigoCred.GetNetworkCredential().Password
|
||||
}
|
||||
|
||||
$AuthBaseAPIUrl = 'https://auth.sso.sectigo.com'
|
||||
|
||||
$tokenEndpoint = $AuthBaseAPIUrl + '/auth/realms/apiclients/protocol/openid-connect/token'
|
||||
$env:SectigoToken = (Invoke-RestMethod -Method Post -Uri $tokenEndpoint -ContentType 'application/x-www-form-urlencoded' -Body $AuthBody).access_token
|
||||
|
||||
Return $env:SectigoToken
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Retrieving OneView Service Account credentials" -Verbose
|
||||
$OVCred = Get-ITDPassword -Title "VMware iLO Service Account" -UserName "ndgov\svcitdvmhpe"
|
||||
Write-Verbose -Message "Confirming OneView is $($OVCred.UserName)" -Verbose
|
||||
|
||||
Import-Module HPEiLOCmdlets -Force
|
||||
|
||||
Write-Verbose -Message "Gather PSUniversal Job Information" -Verbose
|
||||
$PSUJobId = $UAJob.Id
|
||||
|
||||
Write-Verbose -Message "Set static variables" -Verbose
|
||||
$RequesterEmail = 'vmware@nd.gov'
|
||||
$AppName = "Infra-VMware"
|
||||
$ServerTypeCode = "Linux"
|
||||
$Format = "x509CO"
|
||||
$OrgId = 8133 # Sectigo OrgDept ID for State of North Dakota Information Technology Department - Cloud & Infrastructure
|
||||
$CertType = 2375 # Sectigo Cert Type for Standard SSL Multi-Domain
|
||||
|
||||
$BaseAPIUrl = 'https://admin.hard.sectigo.com'
|
||||
|
||||
switch ($PSCmdlet.ParameterSetName) {
|
||||
'ByHostName' {
|
||||
$AllILOServers = $HostName
|
||||
}
|
||||
'SynergyDiscovery' {
|
||||
Write-Verbose -Message "Retrieving iLO information from OneView/Synergy" -Verbose
|
||||
$OneviewServers = @('itdmdnsyncompt1.nd.gov', 'itdmdnsyncompp1.nd.gov', 'itdbissyncompp1.nd.gov')
|
||||
$AllILOServers = @()
|
||||
ForEach ($OneviewServer in $OneViewServers ) {
|
||||
Write-Verbose -Message "Connecting to OneView server $OneviewServer" -Verbose
|
||||
Connect-OVMgmt -Hostname $OneviewServer -Credential $OVCred -AuthLoginDomain nd.gov -LoginAcknowledge
|
||||
$AllILOServers += (Get-OVServer).ServerName | ForEach-Object {
|
||||
$_.split('.')[0] + "lo.nd.gov"
|
||||
}
|
||||
Disconnect-OVMgmt
|
||||
}
|
||||
}
|
||||
default {
|
||||
Write-Error -Message "Invalid parameter set."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Checking iLO certificates for expiration or invalid issuer/commonname" -Verbose
|
||||
$AlliLOToRenew = @()
|
||||
ForEach ($iLODnsName in $AllILOServers) {
|
||||
Write-Verbose -Message "Checking certificate for iLO $iLODnsName" -Verbose
|
||||
$cert = Get-SslCertificate -DNSName $iLODnsName
|
||||
|
||||
If ( $cert.NotAfter.AddDays(-7) -le (Get-Date) ) {
|
||||
Write-Warning -Message "Certificate for $iLODnsName expires on $($cert.NotAfter), add to renewal list."
|
||||
$AlliLOToRenew += $iLODnsName
|
||||
}
|
||||
|
||||
If ( $cert.subject -notlike "*.nd.gov*") {
|
||||
Write-Warning -Message "Certificate for $iLODnsName is not a ND.gov cert, add to renewal list."
|
||||
$AlliLOToRenew += $iLODnsName
|
||||
}
|
||||
}
|
||||
|
||||
ForEach ($iLOToRenew in $AlliLOToRenew | Select-Object -Unique) {
|
||||
Write-Verbose -Message "Processing iLO $iLOToRenew for certificate renewal" -Verbose
|
||||
|
||||
$iLOCred = $null
|
||||
switch ($iLOToRenew) {
|
||||
{ $_ -like "*bis*" } {
|
||||
Write-Verbose -Message "BIS iLO detected, getting credentials for itdbissyncompp1" -Verbose
|
||||
$iloCred = Get-ITDPassword -Title "itdbissyncompp1 iLO" -UserName "Administrator";
|
||||
}
|
||||
{ $_ -like "*mdn*" } {
|
||||
Write-Verbose -Message "MDN iLO detected, getting credentials for itdmdnsyncompp1" -Verbose
|
||||
$iloCred = Get-ITDPassword -Title "itdmdnsyncompp1 iLO" -UserName "Administrator";
|
||||
}
|
||||
{ $_ -like "*test*" } {
|
||||
Write-Verbose -Message "TEST iLO detected, getting credentials for itdmdnsyncompt1" -Verbose
|
||||
$iloCred = Get-ITDPassword -Title "itdmdnsyncompt1 iLO" -UserName "Administrator";
|
||||
}
|
||||
default { Write-Error -Message "No iLO credentials found for $iLOToRenew, skipping."; continue; }
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Verbose -Message "Establishing connection to iLO $iLOToRenew" -Verbose
|
||||
$iLOConnection = Connect-HPEiLO -Address $iLOToRenew -Credential $iLOCred -DisableCertificateAuthentication
|
||||
Write-Verbose -Message "Generating CSR on iLO $iLOToRenew" -Verbose
|
||||
Start-HPEiLOCertificateSigningRequest -Connection $iLOConnection `
|
||||
-CommonName $iLOConnection.Hostname `
|
||||
-Organization "State of North Dakota" `
|
||||
-Country US `
|
||||
-City Bismarck `
|
||||
-State "North Dakota"
|
||||
|
||||
Start-Sleep -Seconds 30 ### for some reason if you check iLO for CSR too frequently it doesn't work
|
||||
|
||||
Write-Verbose -Message "Getting CSR for $iLOToRenew"
|
||||
$CsrData = $null
|
||||
|
||||
While ($null -eq $CsrData.CertificateSigningRequest) {
|
||||
try {
|
||||
$CsrData = Get-HPEiLOCertificateSigningRequest -Connection $iLOConnection
|
||||
}
|
||||
catch {
|
||||
Write-Warning -Message "CSR not ready yet for $iLOToRenew, waiting 10 seconds."
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
}
|
||||
Disconnect-HPEiLO -Connection $iLOConnection
|
||||
$iLOConnection = $null
|
||||
|
||||
Write-Verbose -Message "Submitting CSR to Sectigo for $iLOToRenew" -Verbose
|
||||
#Get-SectigoToken ## function above loaded into memory
|
||||
|
||||
$EnrollBody = @{
|
||||
orgId = $OrgId;
|
||||
certType = $CertType
|
||||
term = 365;
|
||||
comments = "iLO Certificate Renewal for $iLOToRenew"
|
||||
serverType = $ServerTypeCode
|
||||
csr = $CsrData.CertificateSigningRequest
|
||||
externalRequester = "vmware@nd.gov"
|
||||
customFields = @(
|
||||
@{
|
||||
name = 'ApplicationName'
|
||||
value = 'Infra-VMware'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$EnrollParams = @{
|
||||
Method = 'Post'
|
||||
Uri = $BaseAPIUrl + "/api/ssl/v1/enroll"
|
||||
Headers = @{
|
||||
"Authorization" = ("Bearer " + (Get-SectigoToken))
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
Body = ($EnrollBody | ConvertTo-Json -Depth 10)
|
||||
ContentType = 'application/json'
|
||||
}
|
||||
|
||||
$EnrollResponse = Invoke-RestMethod @EnrollParams
|
||||
$OrderId = $EnrollResponse.sslId
|
||||
|
||||
Write-Verbose -Message "Waiting for certificate issuance for $iLOToRenew" -Verbose
|
||||
$Certificate = $null
|
||||
|
||||
Start-Sleep -Seconds 15
|
||||
|
||||
While ($Certificate.status -ne "Issued") {
|
||||
$ValidateUrl = "${BaseAPIUrl}/api/ssl/v1/${OrderId}"
|
||||
$ValidateSplat = @{
|
||||
Uri = $ValidateUrl
|
||||
Method = 'Get'
|
||||
Headers = @{
|
||||
"Authorization" = ("Bearer " + (Get-SectigoToken))
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
$Certificate = Invoke-RestMethod @ValidateSplat
|
||||
If ($Certificate.status -ne "Issued") {
|
||||
Write-Warning -Message "Certificate for $iLOToRenew not issued yet, waiting 15 seconds."
|
||||
Start-Sleep -Seconds 15
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Downloading issued certificate for $iLOToRenew" -Verbose
|
||||
$CollectUrl = $BaseAPIUrl + "/api/ssl/v1/collect/${OrderId}?format=${Format}"
|
||||
|
||||
$CommonName = $Certificate.commonName
|
||||
|
||||
$DownloadSplat = @{
|
||||
Uri = $CollectUrl
|
||||
Method = 'Get'
|
||||
Headers = @{
|
||||
"Authorization" = ("Bearer " + (Get-SectigoToken))
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
UseBasicParsing = $true
|
||||
}
|
||||
|
||||
Write-Verbose -Message "Downloading certificate to F:\iLO_certs\$CommonName-$OrderId-$PSUJobId.pem" -Verbose
|
||||
Invoke-WebRequest @DownloadSplat -OutFile "F:\iLO_certs\$CommonName-$OrderId-$PSUJobId.pem"
|
||||
|
||||
Write-Verbose -Message "Importing new certificate to iLO $iLOToRenew" -Verbose
|
||||
$CertificateToUpload = Get-Content -Path "F:\iLO_certs\$CommonName-$OrderId-$PSUJobId.pem" -Raw
|
||||
$iLOConnection = Connect-HPEiLO -Address $iLOToRenew -Credential $iLOCred -DisableCertificateAuthentication -Verbose
|
||||
Import-HPEiLOCertificate -Certificate ($CertificateToUpload | Out-String) -Connection $iLOConnection -Force
|
||||
Disconnect-HPEiLO -Connection $iLOConnection
|
||||
|
||||
Write-Verbose -Message "Disconnecting from iLO $iLOToRenew" -Verbose
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user