In the previous blog I’ve showed how to upload data to IT-Glue by creating a new flexible asset with two fields: a name field on which we perform a match if a document exists, and a field where we placed some data. This time, we’re going to upload an entire HTML file we’ll create by gathering data from the server we run the script on. So, let’s get started
The scripts below can also be downloaded for your RMM system, find the AMP file here.
Data gathering
First, We’ll make a list of all the data that is imported for us to have documented. For me, that would be the following list of information:
- Server Name
- Server type (A physical or virtual machine)
- How much RAM the machine currently has assigned
- The NIC configuration
- What applications are installed
- What server roles are installed
- What the physical disk layout is
- What the RAID layout is
Let’s get started with each of the data collection parts. In the following script we check if the model contains “Virtual” or “VMWare”. two signs that show if a machine is a physical or virtual machine.
$ComputerSystemInfo = Get-CimInstance -ClassName Win32_ComputerSystem if($ComputerSystemInfo.model -match "Virtual" -or $ComputerSystemInfo.model -match "VMware") { $MachineType = "Virtual"} Else { $MachineType = "Physical"}
To get the amount of RAM we run the following oneliner
$RAM = (systeminfo | Select-String 'Total Physical Memory:').ToString().Split(':')[1].Trim()
Applications and roles are also quite easy to get
$ApplicationsFrag = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Convertto-html -Fragment | select -skip 1 $ApplicationsTable = "<br/><table class=`"table table-bordered table-hover`" >" + $ApplicationsFrag $RolesFrag = Get-WindowsFeature | Where-Object {$_.Installed -eq $True} | Select-Object displayname,name | convertto-html -Fragment | Select-Object -Skip 1 $RolesTable = "<br/><table class=`"table table-bordered table-hover`" >" + $RolesFrag
For the NIC configuration, we need to do a little more work. We want the NIC configuration to show both IPv4 and IPv6 configurations. We also want to look up all NIC configurations, if a server has multiple NICs.
$networkName = Get-CimInstance -ClassName Win32_NetworkAdapter | Where-Object {$_.PhysicalAdapter -eq "True"} | Sort Index $networkIP = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object {$_.MACAddress -gt 0} | Sort Index $networkSummary = New-Object -TypeName 'System.Collections.ArrayList' foreach($nic in $networkName) { $nic_conf = $networkIP | Where-Object {$_.Index -eq $nic.Index} $networkDetails = New-Object PSObject -Property @{ Index = [int]$nic.Index; AdapterName = [string]$nic.NetConnectionID; Manufacturer = [string]$nic.Manufacturer; Description = [string]$nic.Description; MACAddress = [string]$nic.MACAddress; IPEnabled = [bool]$nic_conf.IPEnabled; IPAddress = [string]$nic_conf.IPAddress; IPSubnet = [string]$nic_conf.IPSubnet; DefaultGateway = [string]$nic_conf.DefaultIPGateway; DHCPEnabled = [string]$nic_conf.DHCPEnabled; DHCPServer = [string]$nic_conf.DHCPServer; DNSServerSearchOrder = [string]$nic_conf.DNSServerSearchOrder; } $networkSummary += $networkDetails } $NicRawConf = $networkSummary | select AdapterName,IPaddress,IPSubnet,DefaultGateway,DNSServerSearchOrder,MACAddress | Convertto-html -Fragment | select -Skip 1 $NicConf = "<br/><table class=`"table table-bordered table-hover`" >" + $NicRawConf
If a server is a physical machine, and a Dell server I also want to see the disk configuration. For this, you’ll need OpenManage installed.
if($machineType -eq "Physical" -and $ComputerSystemInfo.Manufacturer -matchf "Dell"){ $DiskLayoutRaw = omreport storage pdisk controller=0 -fmt cdv $DiskLayoutSemi = $DiskLayoutRaw | select-string -SimpleMatch "ID,Status," -context 0,($DiskLayoutRaw).Length | convertfrom-csv -Delimiter "," | select Name,Status,Capacity,State,"Bus Protocol","Product ID","Serial No.","Part Number",Media | convertto-html -Fragment $DiskLayoutTable = "<br/><table class=`"table table-bordered table-hover`" >" + $DiskLayoutsemi #Try to get RAID layout $RAIDLayoutRaw = omreport storage vdisk controller=0 -fmt cdv $RAIDLayoutSemi = $RAIDLayoutRaw | select-string -SimpleMatch "ID,Status," -context 0,($RAIDLayoutRaw).Length | convertfrom-csv -Delimiter "," | select Name,Status,State,Layout,"Device Name","Read Policy","Write Policy",Media | convertto-html -Fragment $RAIDLayoutTable = "<br/><table class=`"table table-bordered table-hover`" >" + $RAIDLayoutsemi }else { $RAIDLayoutTable = "Could not get physical disk info" $DiskLayoutTable = "Could not get physical disk info" }
Putting all of that together will give us the following script, I’ve also added some headers to make the output HTML file look nice. You can use this as a stand-alone script.
#Server documentation script $ComputerSystemInfo = Get-CimInstance -ClassName Win32_ComputerSystem if($ComputerSystemInfo.model -match "Virtual" -or $ComputerSystemInfo.model -match "VMware") { $MachineType = "Virtual"} Else { $MachineType = "Physical"} $networkName = Get-CimInstance -ClassName Win32_NetworkAdapter | Where-Object {$_.PhysicalAdapter -eq "True"} | Sort Index $networkIP = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object {$_.MACAddress -gt 0} | Sort Index $networkSummary = New-Object -TypeName 'System.Collections.ArrayList' foreach($nic in $networkName) { $nic_conf = $networkIP | Where-Object {$_.Index -eq $nic.Index} $networkDetails = New-Object PSObject -Property @{ Index = [int]$nic.Index; AdapterName = [string]$nic.NetConnectionID; Manufacturer = [string]$nic.Manufacturer; Description = [string]$nic.Description; MACAddress = [string]$nic.MACAddress; IPEnabled = [bool]$nic_conf.IPEnabled; IPAddress = [string]$nic_conf.IPAddress; IPSubnet = [string]$nic_conf.IPSubnet; DefaultGateway = [string]$nic_conf.DefaultIPGateway; DHCPEnabled = [string]$nic_conf.DHCPEnabled; DHCPServer = [string]$nic_conf.DHCPServer; DNSServerSearchOrder = [string]$nic_conf.DNSServerSearchOrder; } $networkSummary += $networkDetails } $NicRawConf = $networkSummary | select AdapterName,IPaddress,IPSubnet,DefaultGateway,DNSServerSearchOrder,MACAddress | Convertto-html -Fragment | select -Skip 1 $NicConf = "<br/><table class=`"table table-bordered table-hover`" >" + $NicRawConf $RAM = (systeminfo | Select-String 'Total Physical Memory:').ToString().Split(':')[1].Trim() $ApplicationsFrag = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Convertto-html -Fragment | select -skip 1 $ApplicationsTable = "<br/><table class=`"table table-bordered table-hover`" >" + $ApplicationsFrag $RolesFrag = Get-WindowsFeature | Where-Object {$_.Installed -eq $True} | Select-Object displayname,name | convertto-html -Fragment | Select-Object -Skip 1 $RolesTable = "<br/><table class=`"table table-bordered table-hover`" >" + $RolesFrag if($machineType -eq "Physical" -and $ComputerSystemInfo.Manufacturer -match "Dell"){ $DiskLayoutRaw = omreport storage pdisk controller=0 -fmt cdv $DiskLayoutSemi = $DiskLayoutRaw | select-string -SimpleMatch "ID,Status," -context 0,($DiskLayoutRaw).Length | convertfrom-csv -Delimiter "," | select Name,Status,Capacity,State,"Bus Protocol","Product ID","Serial No.","Part Number",Media | convertto-html -Fragment $DiskLayoutTable = "<br/><table class=`"table table-bordered table-hover`" >" + $DiskLayoutsemi #Try to get RAID layout $RAIDLayoutRaw = omreport storage vdisk controller=0 -fmt cdv $RAIDLayoutSemi = $RAIDLayoutRaw | select-string -SimpleMatch "ID,Status," -context 0,($RAIDLayoutRaw).Length | convertfrom-csv -Delimiter "," | select Name,Status,State,Layout,"Device Name","Read Policy","Write Policy",Media | convertto-html -Fragment $RAIDLayoutTable = "<br/><table class=`"table table-bordered table-hover`" >" + $RAIDLayoutsemi }else { $RAIDLayoutTable = "Could not get physical disk info" $DiskLayoutTable = "Could not get physical disk info" } #Head for HTML $head = @" <Title>Server Log Report</Title> <style> body { background-color:#E5E4E2; font-family:Monospace; font-size:10pt; } td, th { border:0px solid black; border-collapse:collapse; white-space:pre; } th { color:white; background-color:black; } table, tr, td, th { padding: 2px; margin: 0px; white-space:pre; } tr:nth-child(odd) {background-color: lightgray} table { width:95%;margin-left:5px; margin-bottom:20px; } h2 { font-family:Tahoma; color:#6D7B8D; } .footer { color:green; margin-left:10px; font-family:Tahoma; font-size:8pt; font-style:italic; } </style> "@ $HTMLFile = @" $head <b>Servername</b>: $ENV:COMPUTERNAME <br> <b>Server Type</b>: $machineType <br> <b>Amount of RAM</b>: $RAM <br> <br> <h1>NIC Configuration</h1> <br> $NicConf <br> <h1>Installed Applications</h1> <br> $ApplicationsTable <br> <h1>Installed Roles</h1> <br> $RolesTable <br> <h1>Physical Disk information</h1> $DiskLayoutTable <h1>RAID information</h1> $RAIDLayoutTable "@ $HTMLFile | out-file C:\Temp\ServerDoc.html
To upload this data to IT-Glue, we can use the exact same script as we’ve used last time, with two small edits; the title of the flexible asset, and the description.
##################################################################### $APIKEy = "YOUR API KEY GOES HERE" $APIEndpoint = "https://api.eu.itglue.com" $orgID = "THE ORGANISATIONID YOU WOULD LIKE TO UPDATE GOES HERE" $FlexAssetName = "ITGLue AutoDoc - Server Overview" $Description = "a server one-page document that shows the current configuration" ##################################################################### #This is the object we'll be sending to IT-Glue. $ComputerSystemInfo = Get-CimInstance -ClassName Win32_ComputerSystem if($ComputerSystemInfo.model -match "Virtual" -or $ComputerSystemInfo.model -match "VMware") { $MachineType = "Virtual"} Else { $MachineType = "Physical"} $networkName = Get-CimInstance -ClassName Win32_NetworkAdapter | Where-Object {$_.PhysicalAdapter -eq "True"} | Sort Index $networkIP = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object {$_.MACAddress -gt 0} | Sort Index $networkSummary = New-Object -TypeName 'System.Collections.ArrayList' foreach($nic in $networkName) { $nic_conf = $networkIP | Where-Object {$_.Index -eq $nic.Index} $networkDetails = New-Object PSObject -Property @{ Index = [int]$nic.Index; AdapterName = [string]$nic.NetConnectionID; Manufacturer = [string]$nic.Manufacturer; Description = [string]$nic.Description; MACAddress = [string]$nic.MACAddress; IPEnabled = [bool]$nic_conf.IPEnabled; IPAddress = [string]$nic_conf.IPAddress; IPSubnet = [string]$nic_conf.IPSubnet; DefaultGateway = [string]$nic_conf.DefaultIPGateway; DHCPEnabled = [string]$nic_conf.DHCPEnabled; DHCPServer = [string]$nic_conf.DHCPServer; DNSServerSearchOrder = [string]$nic_conf.DNSServerSearchOrder; } $networkSummary += $networkDetails } $NicRawConf = $networkSummary | select AdapterName,IPaddress,IPSubnet,DefaultGateway,DNSServerSearchOrder,MACAddress | Convertto-html -Fragment | select -Skip 1 $NicConf = "<br/><table class=`"table table-bordered table-hover`" >" + $NicRawConf $RAM = (systeminfo | Select-String 'Total Physical Memory:').ToString().Split(':')[1].Trim() $ApplicationsFrag = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Convertto-html -Fragment | select -skip 1 $ApplicationsTable = "<br/><table class=`"table table-bordered table-hover`" >" + $ApplicationsFrag $RolesFrag = Get-WindowsFeature | Where-Object {$_.Installed -eq $True} | Select-Object displayname,name | convertto-html -Fragment | Select-Object -Skip 1 $RolesTable = "<br/><table class=`"table table-bordered table-hover`" >" + $RolesFrag if($machineType -eq "Physical" -and $ComputerSystemInfo.Manufacturer -match "Dell"){ $DiskLayoutRaw = omreport storage pdisk controller=0 -fmt cdv $DiskLayoutSemi = $DiskLayoutRaw | select-string -SimpleMatch "ID,Status," -context 0,($DiskLayoutRaw).Length | convertfrom-csv -Delimiter "," | select Name,Status,Capacity,State,"Bus Protocol","Product ID","Serial No.","Part Number",Media | convertto-html -Fragment $DiskLayoutTable = "<br/><table class=`"table table-bordered table-hover`" >" + $DiskLayoutsemi #Try to get RAID layout $RAIDLayoutRaw = omreport storage vdisk controller=0 -fmt cdv $RAIDLayoutSemi = $RAIDLayoutRaw | select-string -SimpleMatch "ID,Status," -context 0,($RAIDLayoutRaw).Length | convertfrom-csv -Delimiter "," | select Name,Status,State,Layout,"Device Name","Read Policy","Write Policy",Media | convertto-html -Fragment $RAIDLayoutTable = "<br/><table class=`"table table-bordered table-hover`" >" + $RAIDLayoutsemi }else { $RAIDLayoutTable = "Could not get physical disk info" $DiskLayoutTable = "Could not get physical disk info" } $HTMLFile = @" <b>Servername</b>: $ENV:COMPUTERNAME <br> <b>Server Type</b>: $machineType <br> <b>Amount of RAM</b>: $RAM <br> <br> <h1>NIC Configuration</h1> <br> $NicConf <br> <h1>Installed Applications</h1> <br> $ApplicationsTable <br> <h1>Installed Roles</h1> <br> $RolesTable <br> <h1>Physical Disk information</h1> $DiskLayoutTable <h1>RAID information</h1> $RAIDLayoutTable "@ $FlexAssetBody = @{ type = 'flexible-assets' attributes = @{ name = $FlexAssetName traits = @{ "name" = $ENV:COMPUTERNAME "information" = $HTMLFile } } } #ITGlue upload starts here. If(Get-Module -ListAvailable -Name "ITGlueAPI") {Import-module ITGlueAPI} Else { install-module ITGlueAPI -Force; import-module ITGlueAPI} #Settings IT-Glue logon information Add-ITGlueBaseURI -base_uri $APIEndpoint Add-ITGlueAPIKey $APIKEy #Checking if the FlexibleAsset exists. If not, create a new one. $FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data if(!$FilterID){ $NewFlexAssetData = @{ type = 'flexible-asset-types' attributes = @{ name = $FlexAssetName icon = 'sitemap' description = $description } relationships = @{ "flexible-asset-fields" = @{ data = @( @{ type = "flexible_asset_fields" attributes = @{ order = 1 name = "name" kind = "Text" required = $true "show-in-list" = $true "use-for-title" = $true } }, @{ type = "flexible_asset_fields" attributes = @{ order = 2 name = "information" kind = "Textbox" required = $false "show-in-list" = $false } } ) } } } New-ITGlueFlexibleAssetTypes -Data $NewFlexAssetData $FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data } #Upload data to IT-Glue. We try to match the Server name to current computer name. $ExistingFlexAsset = (Get-ITGlueFlexibleAssets -filter_flexible_asset_type_id $Filterid.id -filter_organization_id $orgID).data | Where-Object {$_.attributes.name -eq $ENV:COMPUTERNAME} #If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload. if(!$ExistingFlexAsset){ $FlexAssetBody.attributes.add('organization-id', $orgID) $FlexAssetBody.attributes.add('flexible-asset-type-id', $FilterID.id) Write-Host "Creating new flexible asset" New-ITGlueFlexibleAssets -data $FlexAssetBody } else { Write-Host "Updating Flexible Asset" Set-ITGlueFlexibleAssets -id $ExistingFlexAsset.id -data $FlexAssetBody}
And that’s it! Happy PowerShelling! the next chapter will be how to upload the Bitlocker key for a client machine to IT-Glue as a password object, it will also tag all related devices, something that’s pretty cool!
You’re aware that Get-WmiObject is not supported in powershell 6.x. You need to use Get-CimInstance, which also works is ps 5.1.
Thanks! the script was tested on ps5.1, I’ll replace the get-wmiobject with get-ciminstance. our internal version of the script already used it 🙂
Pingback: Documenting with PowerShell: Chapter 2 – Documenting Bitlocker keys - CyberDrain
Pingback: Dowst.Dev | PowerShell Weekly – August 26, 2019
I love this. Do you know if there is any way of having it tag the server configuration within IT Glue as part of the script?
Hi! Yes it is possible to tag the device. Its best to combine the chapter 2 script found here, together with this one: https://www.cyberdrain.com/documenting-with-powershell-chapter-2-documenting-bitlocker-keys/
Kelvin – what another option script. I started using it earlier this week. Is there anyway to get it to add the server name as well as the share name? We have clients with multiple file servers and adding that would be awesome.
Pingback: Monitoring and Documenting with PowerShell: End of year review - CyberDrain
Hey Kelvin, thank you so much for all of your insights here.
I’m running this script through Datto RMM, and I have the API key, Customer ID, and API address entered correctly.
I get these errors (I should mention, this error log may not be from this exact script, but I’ve tried many of them with similar results)
The term ‘install-module’ is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if a path
was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:7 char:96
+ If(Get-Module -ListAvailable -Name “ITGlueAPI”) {Import-module ITGlueAPI} Els
e { install-module <<<< ITGlueAPI -Force; import-module ITGlueAPI}
+ CategoryInfo : ObjectNotFound: (install-module:String) [], Comm
andNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Import-Module : The specified module 'ITGlueAPI' was not loaded because no vali
d module file was found in any module directory.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:7 char:128
+ If(Get-Module -ListAvailable -Name "ITGlueAPI") {Import-module ITGlueAPI} Els
e { install-module ITGlueAPI -Force; import-module <<<< ITGlueAPI}
+ CategoryInfo : ResourceUnavailable: (ITGlueAPI:String) [Import-
Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Comm
ands.ImportModuleCommand
The term 'Add-ITGlueBaseURI' is not recognized as the name of a cmdlet, functio
n, script file, or operable program. Check the spelling of the name, or if a pa
th was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:9 char:18
+ Add-ITGlueBaseURI <<<< -base_uri $APIEndpoint
+ CategoryInfo : ObjectNotFound: (Add-ITGlueBaseURI:String) [], C
ommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The term 'Add-ITGlueAPIKey' is not recognized as the name of a cmdlet, function
, script file, or operable program. Check the spelling of the name, or if a pat
h was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:10 char:17
+ Add-ITGlueAPIKey <<<< $APIKEy
+ CategoryInfo : ObjectNotFound: (Add-ITGlueAPIKey:String) [], Co
mmandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The term 'Get-BitLockerVolume' is not recognized as the name of a cmdlet, funct
ion, script file, or operable program. Check the spelling of the name, or if a
path was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:12 char:38
+ $BitlockVolumes = Get-BitLockerVolume <<<<
+ CategoryInfo : ObjectNotFound: (Get-BitLockerVolume:String) [],
CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The term 'Get-ITGlueConfigurations' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the name, or
if a path was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:14 char:44
+ $TaggedResource = (Get-ITGlueConfigurations <<<< -organization_id $orgID -fi
lter_serial_number (get-ciminstance win32_bios).serialnumber).data
+ CategoryInfo : ObjectNotFound: (Get-ITGlueConfigurations:String
) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Cannot index into a null array.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:21 char:69
+ password = $BitlockVolume.KeyProtector.recoverypassword[ <<<< 1]
+ CategoryInfo : InvalidOperation: (1:Int32) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
The term 'Get-ITGluePasswords' is not recognized as the name of a cmdlet, funct
ion, script file, or operable program. Check the spelling of the name, or if a
path was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:32 char:46
+ $ExistingPasswordAsset = (Get-ITGluePasswords <<<< -filter_organization_id $
orgID -filter_name $PasswordObjectName).data
+ CategoryInfo : ObjectNotFound: (Get-ITGluePasswords:String) [],
CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The term 'New-ITGluePasswords' is not recognized as the name of a cmdlet, funct
ion, script file, or operable program. Check the spelling of the name, or if a
path was included, verify that the path is correct and try again.
At C:\ProgramData\CentraStage\Packages\76b060cb-58a1-4b5e-b25a-2e19cf132cda#\co
mmand.ps1:36 char:38
+ $ITGNewPassword = New-ITGluePasswords <<<< -organization_id $orgID -data $Pa
sswordObject
+ CategoryInfo : ObjectNotFound: (New-ITGluePasswords:String) [],
CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I also get these error messages when running via Datto RMM. Any ideas?
Resolving in the coming week. 🙂
Just a note – the error “The term ‘install-module’ is not recognized as the name of a cmdlet, function,
script file, or operable program.” means you’re running the script on an unsupported OS/PowerShell version. I will try to fix this, but can’t guarantee anything.
I’ve found that if you run the script locally within Powershell, it prompts you to install a module. You can type “Y” to install. After you do this manual run, the automatic run will work.
Assumedly, the prompt is hanging up the script. This occurs on all 5 that I’ve tested.
There’s got to be a way to force install that module, but I’m stabbing in the dark with no real experience in PS.
Try adding this to the top of the script and rerunning it:
Import-Module PowerShellGet
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Set-PSRepository -InstallationPolicy Trusted -Name PSGallery
Nice. Thank you Kelvin!
Awesome script here as always man.
Question though with different manufacturers.
Have you looed into possibly adding support for HPE Proliant servers?