Excel report Formatting in PowerShell - excel

Need help to create a script to get a HPOA server blade Health report
The problem is that when I get query Health it outputs in a PSO object with fields (IP,Health,Blades(#{Blade1 Health}{Blade2 Health}{3} . . .) )
I want a report like below
IP Bay Power Health
-- --- ----- -----
10.3.131.2 1 On OK
2 On OK
3 On OK
4 On OK
5 On Degraded
The variables are derived as below .
$sstaInfo = {} | Select IP, Bay, Power, Health, DeviceFailure
$sstaInfo.IP=$ssta.IP (Gives a single IP output)
$sstaInfo.Bay=$sstaBlades.Bay $sstaInfo.Power=$sstaBlades.Power
$sstaInfo.Health=$sstaBlades.Health
How can I get this working ?
$ssta variable has the below output :
#{Power=On; CurrentWattageUsed=480; Health=OK; UnitIdentificationLED=Off; VirtualFan=33%; DiagnosticStatus=; Bay=1} #{Power=On; CurrentWattageUsed=576; Health=OK; UnitIdentificationLED=Off; VirtualFan=47%; DiagnosticStatus=; Bay=2}
#------------------------------------------------------------ Input Variable Definations
$HPOAServers =#(
[pscustomobject]#{Name='10.11.12.13'},
[pscustomobject]#{Name='10.11.12.14'}
)
$Username ="admin"
$Password ="admin"
#------------------------------------------------------------ Main Script Starts Here
# Function for connecting to OA and returning connection object on success
foreach ($HPOAServer in $HPOAServers) {
$con = Connect-HPOA $HPOAServer.Name -username $Username -password $Password
$report = #()
$ssta = Get-HPOAServerStatus -Bay All $con
$sstaBlade=$ssta.Blade
Write-Host $sstaBlade
Foreach ($sstaBlades in $sstaBlade) {
$i++
$sstaInfo = {} | Select IP, Bay, Power, Health, DeviceFailure
$sstaInfo.IP=$ssta.IP
$sstaInfo.Bay=$sstaBlades.Bay
$sstaInfo.Power=$sstaBlades.Power
$sstaInfo.Health=$sstaBlades.Health
$sstaInfo.DeviceFailure=$ssta.Blade.DiagnosticStatus.DeviceFailure
}
$report += $ssta | Select-Object -Property IP
$report += $ssta.Blade | Select-Object -Property Bay, Power, Health | Format-Table *
$report | out-file "HPOA_Health_Report.txt" -Append
}
Disconnect-HPOA $con

I suggest you use Export-CSV instead, so below line
$report | Out-File "HPOA_Health_Report.txt" -Append
will be replaced by:
$report | Export-Csv "HPOA_Health_Report.csv" -Append -NoTypeInformation

Function HPOA () {
try
{
Remove-Item -Path $outputfile -Force
foreach ($HPOAServer in $HPOAServers)
{
$con = Connect-HPOA $HPOAServer.Name -username $Username -password $Password -ErrorAction 'Stop'
$ssta = Get-HPOAServerStatus -Bay All $con
$ssta.Blade | Foreach-Object {
$sstaInfo = $_
$sstaInfo | Select-Object -Property #{Name="Chassis_IP_Address";Expression={$ssta.IP}},
#{Name="Blade_Power_Status";Expression={$_.Power}},
#{Name="Blade_Bay_Number";Expression={$_.Bay}},
#{Name="Blade_Health_Status";Expression={$_.Health}},
#{Name="Blade_Diagnostic_DeviceFailure_Status";Expression={$ssta.Blade.DiagnosticStatus.DeviceFailure}}
} | ConvertTo-Html -Title " $HPOARegionName HPOA Health Report " -Head $Header -Body "<H2> $HPOARegionName HPOA Health Report </H2>" -As Table | Out-File -Append $outputfile
Disconnect-HPOA $con
}
}
catch
{
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
Write-Host $ErrorMessage
Write-Host $FailedItem
}
}
HPOA

Related

My Powershell script is not explicitly calling for any specific resources but I am getting "ErrorCode: TargetResourceNotFound" error

My Powershell script is not explicitly calling for any specific resources but I am getting "ErrorCode: TargetResourceNotFound" error. I have attached the error in the image. What am I missing?
$subs = Get-AzSubscription | Where-Object {$_.Name -like "*-NonProd"}
foreach ($sub in $subs)
{
Select-AzSubscription -SubscriptionId $sub.Id
$RGs = Get-AzResourceGroup | Where-Object {$_.ResourceGroupName -like "*Infra"}
foreach ($RG in $RGs)
{
$NetworkWatchers = Get-AzNetworkWatcher
$NSGs = (Get-AzNetworkSecurityGroup).Id
foreach ($NSG in $NSGs)
{
foreach ($NetworkWatcher in $NetworkWatchers)
{
$Status = Get-AzNetworkWatcherFlowLogStatus -NetworkWatcherName $NetworkWatcher.Name
ResourceGroupName $RG.ResourceGroupName -TargetResourceId $NSG -Verbose
}
if (($Status).Enabled -eq $true)
{
Write-Output "$NSG in $(($sub).Name) has FlowLogs Enabled" | Tee-Object -FilePath 'C:\Users\user1\downloads\Output.txt' -Verbose -Append
}
if (($Status).Enabled -ne $true)
{
Write-Output "$NSG in $(($sub).Name) does not have FlowLogs Enabled" | Tee-Object -FilePath 'C:\Users\user1\downloads\Output.txt' -Verbose -Append
}
}
}
}
enter code here
Error Attached
Network Watchers are usually in a hidden resource group, and perhaps you are trying to find one in one of the available RGs. Try omitting the RG factor and use
$subs = Get-AzSubscription | Where-Object { $_.Name -like "*-NonProd" }
foreach ($sub in $subs) {
Select-AzSubscription -SubscriptionId $sub.Id
$NetworkWatchers = Get-AzNetworkWatcher
$NSGs = (Get-AzNetworkSecurityGroup).Id
foreach ($NSG in $NSGs) {
foreach ($NetworkWatcher in $NetworkWatchers) {
$Status = Get-AzNetworkWatcherFlowLogStatus -NetworkWatcher $NetworkWatcher -TargetResourceId $NSG -Verbose
}
if (($Status).Enabled -eq $true) {
Write-Output "$NSG in $(($sub).Name) has FlowLogs Enabled" | Tee-Object -FilePath 'C:\Users\user1\downloads\Output.txt' -Verbose -Append
}
if (($Status).Enabled -ne $true) {
Write-Output "$NSG in $(($sub).Name) does not have FlowLogs Enabled" | Tee-Object -FilePath 'C:\Users\user1\downloads\Output.txt' -Verbose -Append
}
}
}
I am able to get all flow logs configuration status.
Here is another approach:
I tried to reproduce the same in my environment and got the same error as below:
The error TargetResourceNotFound usually occurs if you are passing invalid resource group name or subscription name.
To confirm whether the resource group or subscription exists, execute the below code lines separately like below:
$subs = Get-AzSubscription | Where-Object {$_.Name -like "*-name"}
$subs
$RGs = Get-AzResourceGroup | Where-Object {$_.ResourceGroupName -like "*name"}
$RGs
The error states that Target resource identifier /subscriptions/subid/resourceGroups/RG/providers/Microsoft.Network/networkWatchers/*** not found in the region westeurope. Cross-verify whether the Network Watcher exists.
I am able to get the status of the Network Watcher successfully when I passed valid subscription and Resource group like below:
If the error still persists, try excluding the $RGs = Get-AzResourceGroup | Where-Object {$_.ResourceGroupName -like "*name"} and execute.
I appreciate your assistance.
I got it working by changing -NetworkWatcherName $NetworkWatcher.Name to -NetworkWatcherName $NetworkWatcher.ResourceGroupName
foreach ($NSG in $NSGs)
{
# $NSG.Id
# $NSGid = $NSG.Id
foreach ($NetworkWatcher in $NetworkWatchers)
{
$Status = Get-AzNetworkWatcherFlowLogStatus -NetworkWatcherName $NetworkWatcher.ResourceGroupName -ResourceGroupName $RG.ResourceGroupName -TargetResourceId $NSG -Verbose -ErrorAction SilentlyContinue
if (($Status).Enabled -eq $true)
{
Write-Output "$NSG in $(($sub).Name) has FlowLogs Enabled" | Tee-Object -FilePath 'C:\Users\A240379\downloads\OutEnabled.csv' -Verbose -Append
}
if (($Status).Enabled -ne $true)
{
Write-Output "$NSG in $(($sub).Name) does not have FlowLogs Enabled" | Tee-Object -FilePath 'C:\Users\A240379\downloads\OutNotEnabled.csv' -Verbose -Append
}
}
}

How to output hash table query result into Out-GridView?

I wish to export a hashtable result into Out-GridView using the Powershell.
The purpose of the below script is to export the Azure VM tags to Out-GridView, it throws error like the below blank result:
Error on the console:
Out-GridView : Syntax error in PropertyPath 'Syntax error in Binding.Path '[ Product] ' ... '(Tag)'.'.
At line:46 char:19
+ $Output | Out-GridView #Export-Csv -Path c:\temp\1a.csv -appe ...
+ ~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [Out-GridView], InvalidOperationException
+ FullyQualifiedErrorId : ManagementListInvocationException,Microsoft.PowerShell.Commands.OutGridViewCommand
This is the actual script which was executed under the Global Administrator role:
<#
.AUTHOR: https://stackoverflow.com/users/13390556/lukasz-g
#>
$Subscription = Get-AzSubscription | Out-GridView -Title 'Select subscription' -OutputMode 'Multiple'
# Initialise output array
$Output = #()
if ($Subscription) {
foreach ($item in $Subscription) {
$item | Select-AzSubscription
# Collect all the resources or resource groups (comment one of below)
$Resource = Get-AzResource
#$Resource = Get-AzResourceGroup
# Obtain a unique list of tags for these groups collectively
$UniqueTags = $Resource.Tags.GetEnumerator().Keys | Get-Unique -AsString | Sort-Object | Select-Object -Unique | Where-Object { $_ -notlike "hidden-*" }
# Loop through the resource groups
foreach ($ResourceGroup in $Resource) {
# Create a new ordered hashtable and add the normal properties first.
$RGHashtable = New-Object System.Collections.Specialized.OrderedDictionary
$RGHashtable.Add("Name", $ResourceGroup.ResourceGroupName)
$RGHashtable.Add("Location", $ResourceGroup.Location)
$RGHashtable.Add("Id", $ResourceGroup.ResourceId)
$RGHashtable.Add("ResourceType", $ResourceGroup.ResourceType)
# Loop through possible tags adding the property if there is one, adding it with a hyphen as it's value if it doesn't.
if ($ResourceGroup.Tags.Count -ne 0) {
$UniqueTags | Foreach-Object {
if ($ResourceGroup.Tags[$_]) {
$RGHashtable.Add("[$_] (Tag)", $ResourceGroup.Tags[$_])
}
else {
$RGHashtable.Add("[$_] (Tag)", "-")
}
}
}
else {
$UniqueTags | Foreach-Object { $RGHashtable.Add("[$_] (Tag)", "-") }
}
# Update the output array, adding the ordered hashtable we have created for the ResourceGroup details.
$Output += New-Object psobject -Property $RGHashtable
}
# Sent the final output to CSV
$Output | Out-GridView #Export-Csv -Path c:\temp\1a.csv -append -NoClobber -NoTypeInformation -Encoding UTF8 -Force
}
}
$RGHashtable.Add("[$_] (Tag)"
In above code, You are trying to add something like below :
In the output
Removed everthing and I tested with simple statements
$Output = #()
$RGHashtable = New-Object System.Collections.Specialized.OrderedDictionary
$RGHashtable.Add("[Testing] (Name)", "Temporary")
$Output += New-Object psobject -Property $RGHashtable
$Output | Out-GridView
I was provided with the same error.
After couple of testing, understood the error only occurs when there is a combination "[SomeString](SomeString)" --- [...](....) in the string.
The Out-GridView is trying to parse the "[<SomeString>](<SomeString>)" and hence the error.
You could try any 1 of the below combination in your code :
$RGHashtable.Add("[$_] [Tag]", $ResourceGroup.Tags[$_])
OR
$RGHashtable.Add("{$_} (Tag)", $ResourceGroup.Tags[$_])
OR
$RGHashtable.Add("[$_] [Tag]", $ResourceGroup.Tags[$_])
This should resolve your issue.
you will have change in 3 instances in your code if I am not wrong.

Struggling to export PowerShell job information to excel

I have a PowerShell script that is running a web request as part of a job. With the script block that is run by the job I am logging the Endpoint Uri and the response time. and then after all the jobs are finished but before I remove the jobs I am trying to export the result into excel.
PS Verion:
Major Minor Build Revision
----- ----- ----- --------
5 1 17763 503
Code:
$Code = {
Param(
[array]$Domain,
[array]$Services,
[array]$TestData,
[string]$Path
)
$Log = #()
## Get Random School ##
$Random = Get-Random -InputObject $TestData -Count 1
## Get Random Service ##
$RandService = Get-Random -InputObject $Services
## Get Random Endpoint ##
$ServiceEndpoints = Get-Content "$Path\Service.Endpoints.json" | Out-String | ConvertFrom-Json
$GatewayEndpoints = $ServiceEndpoints.Services.$RandService.Gateway
$RandomEndpoint = Get-Random -InputObject $GatewayEndpoints
$Headers = #{
"Authorization" = "Bearer" + ' ' + $Random.Token
}
$Uri = 'https://' + $Domain + $RandomEndpoint
Try {
$TimeTaken = Measure-Command -Expression {
$JsonResponse = Invoke-WebRequest -Uri $Uri -Headers $Headers -ContentType 'application/json' -Method Get -UseBasicParsing
}
}
Catch {
}
$ResponseTime = [Math]::Round($TimeTaken.TotalMilliseconds, 1)
$LogItem = New-Object PSObject
$LogItem | Add-Member -type NoteProperty -Name 'Endpoint' -Value $Uri
$LogItem | Add-Member -type NoteProperty -Name 'Time' -Value $ResponseTime
$Log += $LogItem
Write-Host $Log
}
#Remove all jobs
Get-Job | Remove-Job
#Start the jobs. Max 4 jobs running simultaneously.
foreach($Row in $TestData){
While ($(Get-Job -state running).count -ge $MaxThreads){
Start-Sleep -Milliseconds 3
}
Start-Job -Scriptblock $Code -ArgumentList $Domain, $Services, $TestData, $Path
}
#Wait for all jobs to finish.
While ($(Get-Job -State Running).count -gt 0) {
start-sleep 1
}
$Log | Export-XLSX -Path .\Test.Results\Performance\Performance.Test.Log.xlsx -ClearSheet
#Get information from each job.
foreach($Job in Get-Job) {
$Info = Receive-Job -Id ($Job.Id)
}
#Remove all jobs created.
Get-Job | Remove-Job
I cannot seem to get the endpoint uri and the response time out of the script block. When I try to export the $Log, all that happens is it creates an empty excel file.
Write-Host $Log
#{Endpoint=https://domain/customer/v1/years/2019/marks; Time=1233.3}
#{Endpoint=https://domain/customer/v1/years/2019/marks; Time=2131.7}
You've to return $Log in your script block, since $Log lives in another scope. You can return $Log in your $Code script block, and finally, fetch it via Receive-Job.
Change your code to:
$Code = {
...
$Log += $LogItem
Write-Host $Log
$Log # return via pipeline to the caller
}
As Niraj Gajjar's answer suggests you can use Export-Csv cmdlet to create an Excel file:
#Get information from each job.
foreach($Job in Get-Job) {
Receive-Job -Id ($Job.Id) | Export-CSV -Path .\Test.Results\Performance\Performance.Test.Log.xlsx -Append -NoTypeInformation
}
Global variables inside of jobs aren't visible in the main script since job is running in a new session with its own global space.
You can use Export-Csv to open file in excel.
sample code with multiple jobs to CSV format :
$jobs = #() # INITILIZING ARRAY
$jobs += Start-Job { appwiz.cpl } # START JOB 1 AND ADDING TO ARRAY
$jobs += Start-Job { compmgmt.msc } # START JOB 2 AND ADDING TO ARRAY
$jobs += Start-Job { notepad.exe } # START JOB 3 AND ADDING TO ARRAY
foreach ( $job in $jobs) # INTERATION OF JOBS
{
Export-Csv -InputObject $job "C:\result.csv" -Append # SAVING TO FILE
}
Note : There are some internal properties of job which are not converted by CSV because depth is 1 but some basic properties are available.

Wait until all threads complete before running next task

I would wrap everything inside foreach($computer in $computers) in a Start-Job to make them run simultaneously. The only problem is, I need to wait for all the jobs to complete before I do the ConvertTo-Json at the bottom.
$sb = "OU=some,OU=ou,DC=some,DC=domain"
$computers = Get-ADComputer -Filter {(Enabled -eq $true)} -SearchBase "$sb" -Properties *
$hasmanufacturer = New-Object System.Collections.Generic.List[System.Object]
foreach($computer in $computers)
{
$drives = try{#(Get-WMIObject -Class Win32_CDROMDrive -Property * -ComputerName $computer.Name -ErrorAction Stop)} catch {$null}
foreach($drive in $drives)
{
if($drive.Manufacturer)
{
$hasmanufacturer.Add($computer)
continue
}
} # inner foreach
}
ConvertTo-Json $hasmanufacturer
Use a Get-Job | Wait-Job before executing the ConvertTo-Json
How about using the array of computer names as a parameter to Invoke-Command. It will run, by default, 32 concurrent remote sessions. The number can be changed with the -Throttle parameter.
$computers = Get-ADComputer -Filter {(Enabled -eq $true)} -SearchBase "OU=Servers,DC=xxx,DC=com" -Properties Name |
Where-Object { $_.Name -match 'LAX_*' } |
ForEach-Object { $_.Name }
$computers
$j = Invoke-Command `
-ComputerName $computers `
-ScriptBlock { Get-WMIObject -Class Win32_CDROMDrive -Property * -ErrorAction Stop } `
-AsJob
while ( (Get-Job -Id $j.Id).Status -eq 'Running') {}
Get-Job -Id $j.Id | Wait-Job
$results = Receive-Job -Id $j.Id
$results

Display all sites and bindings in PowerShell

I am documenting all the sites and binding related to the site from the IIS. Is there an easy way to get this list through a PowerShell script rather than manually typing looking at IIS?
I want the output to be something like this:
Site Bindings
TestSite www.hello.com
www.test.com
JonDoeSite www.johndoe.site
Try this:
Import-Module Webadministration
Get-ChildItem -Path IIS:\Sites
It should return something that looks like this:
Name ID State Physical Path Bindings
---- -- ----- ------------- --------
ChristophersWeb 22 Started C:\temp http *:8080:ChristophersWebsite.ChDom.com
From here you can refine results, but be careful. A pipe to the select statement will not give you what you need. Based on your requirements I would build a custom object or hashtable.
Try something like this to get the format you wanted:
Get-WebBinding | % {
$name = $_.ItemXPath -replace '(?:.*?)name=''([^'']*)(?:.*)', '$1'
New-Object psobject -Property #{
Name = $name
Binding = $_.bindinginformation.Split(":")[-1]
}
} | Group-Object -Property Name |
Format-Table Name, #{n="Bindings";e={$_.Group.Binding -join "`n"}} -Wrap
If you just want to list all the sites (ie. to find a binding)
Change the working directory to "C:\Windows\system32\inetsrv"
cd c:\Windows\system32\inetsrv
Next run "appcmd list sites" (plural) and output to a file. e.g c:\IISSiteBindings.txt
appcmd list sites > c:\IISSiteBindings.txt
Now open with notepad from your command prompt.
notepad c:\IISSiteBindings.txt
The most easy way as I saw:
Foreach ($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]#{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation}}}
Try this
function DisplayLocalSites
{
try{
Set-ExecutionPolicy unrestricted
$list = #()
foreach ($webapp in get-childitem IIS:\Sites\)
{
$name = "IIS:\Sites\" + $webapp.name
$item = #{}
$item.WebAppName = $webapp.name
foreach($Bind in $webapp.Bindings.collection)
{
$item.SiteUrl = $Bind.Protocol +'://'+ $Bind.BindingInformation.Split(":")[-1]
}
$obj = New-Object PSObject -Property $item
$list += $obj
}
$list | Format-Table -a -Property "WebAppName","SiteUrl"
$list | Out-File -filepath C:\websites.txt
Set-ExecutionPolicy restricted
}
catch
{
$ExceptionMessage = "Error in Line: " + $_.Exception.Line + ". " + $_.Exception.GetType().FullName + ": " + $_.Exception.Message + " Stacktrace: " + $_.Exception.StackTrace
$ExceptionMessage
}
}
function Get-ADDWebBindings {
param([string]$Name="*",[switch]$http,[switch]$https)
try {
if (-not (Get-Module WebAdministration)) { Import-Module WebAdministration }
Get-WebBinding | ForEach-Object { $_.ItemXPath -replace '(?:.*?)name=''([^'']*)(?:.*)', '$1' } | Sort | Get-Unique | Where-Object {$_ -like $Name} | ForEach-Object {
$n=$_
Get-WebBinding | Where-Object { ($_.ItemXPath -replace '(?:.*?)name=''([^'']*)(?:.*)', '$1') -like $n } | ForEach-Object {
if ($http -or $https) {
if ( ($http -and ($_.protocol -like "http")) -or ($https -and ($_.protocol -like "https")) ) {
New-Object psobject -Property #{Name = $n;Protocol=$_.protocol;Binding = $_.bindinginformation}
}
} else {
New-Object psobject -Property #{Name = $n;Protocol=$_.protocol;Binding = $_.bindinginformation}
}
}
}
}
catch {
$false
}
}
I found this page because I needed to migrate a site with many many bindings to a new server. I used some of the code here to generate the powershell script below to add the bindings to the new server. Sharing in case it is useful to someone else:
Import-Module WebAdministration
$Websites = Get-ChildItem IIS:\Sites
$site = $Websites | Where-object { $_.Name -eq 'site-name-in-iis-here' }
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string[]]$Bindings = $BindingInfo.Split(" ")
$i = 0
$header = ""
Do{
[string[]]$Bindings2 = $Bindings[($i+1)].Split(":")
Write-Output ("New-WebBinding -Name `"site-name-in-iis-here`" -IPAddress " + $Bindings2[0] + " -Port " + $Bindings2[1] + " -HostHeader `"" + $Bindings2[2] + "`"")
$i=$i+2
} while ($i -lt ($bindings.count))
It generates records that look like this:
New-WebBinding -Name "site-name-in-iis-here" -IPAddress "*" -Port 80 -HostHeader www.aaa.com
I found this question because I wanted to generate a web page with links to all the websites running on my IIS instance. I used Alexander Shapkin's answer to come up with the following to generate a bunch of links.
$hostname = "localhost"
Foreach ($Site in get-website) {
Foreach ($Bind in $Site.bindings.collection) {
$data = [PSCustomObject]#{
name=$Site.name;
Protocol=$Bind.Protocol;
Bindings=$Bind.BindingInformation
}
$data.Bindings = $data.Bindings -replace '(:$)', ''
$html = "" + $data.name + ""
$html.Replace("*", $hostname);
}
}
Then I paste the results into this hastily written HTML:
<html>
<style>
a { display: block; }
</style>
{paste PowerShell results here}
</body>
</html>

Resources