Escape powershell Variable in HTML - html

I am trying to write a report in powershell and then sent the output to an HTML Page. i am figuring out how can i format my variable from my powershell script to HTML.
If i get an output which is not correct, i will it to have a blue font in my html page.
Here is my powershell script
$os = (get-wmiobject -class win32_operatingsystem).caption
function checkosvers {
if($os -contains "*Server*" ){
write-output "This is a server"}
else{
write-output "Its a $os"}
}
$osvers = checkosvers | foreach {$PSItem -replace ("Its a $os","<font color='blue'>Its a $os</font>")}
$osvers | ConvertTo-Html -Fragment -as List | Out-File -FilePath "C:\Users\XX\Desktop\mypage.html"
if i put a string in place of a varible, it appears blue in my html page
{$PSItem -replace ("Its a $os","Its a $os")}

You could make a filter of it, and just have it output what you want. I would have it accept values from the pipeline for ease of use, so something like this:
filter checkosvers {
param([Parameter(ValueFromPipeline)]$osVer)
if($osVer -match "Server" ){
"This is a server"
}else{
"<font color='blue'>Its a $osVer</font>"}
}
Then you can just pipe things to it like:
PS C:\Windows\system32> 'Microsoft Windows Server 2012 R2 Datacenter'|checkosvers
This is a server
or...
PS C:\WINDOWS\system32> (get-wmiobject -class win32_operatingsystem).caption|checkosvers
<font color='blue'>Its a Microsoft Windows 10 Enterprise</font>

Related

Regex: Starting from a specific point on each line

I have an HTML file that displays software installed on a machine, and I'd like to remove some of the cells in the table in the HTML file.
Below is a sample of the code:
<tr><td>Adobe Acrobat Reader DC</td><td>18.009.20050</td><td>20171130</td><td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td></tr>
<tr><td>Adobe Flash Player 28 ActiveX</td><td>28.0.0.137</td><td></td><td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td></tr>
...and so on.
What I'm trying to accomplish is to delete everything starting from the 4th instance of the td tag and stop just before the closing /tr tag on each line, so essentially eliminating...
<td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td>
<td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td>
...so that I'm left with...
<tr><td>Adobe Acrobat Reader DC</td><td>18.009.20050</td><td>20171130</td></tr>
<tr><td>Adobe Flash Player 28 ActiveX</td><td>28.0.0.137</td><td></td></tr>
The regex that I'm using is
(?<=<td>)(.*)(?=<\/tr>)
The issue I'm having is that the above regex is selecting the enitre line of code. How can I change this so that it's starting from the 4th instance of the tag for each line?
Please see the following link with a full example of the HTML file I'm using and the regex applied: https://regex101.com/r/C9lkMc/3
EDIT 1: This HTML is generated from a PowerShell script to fetch installed software on remote machines. The code for that is:
Invoke-Command -ComputerName $hostname -ScriptBlock {
if (!([Diagnostics.Process]::GetCurrentProcess().Path -match '\\syswow64\\')) {
$unistallPath = "\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
$unistallWow6432Path = "\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"
#(
if (Test-Path "HKLM:$unistallWow6432Path" ) { Get-ChildItem "HKLM:$unistallWow6432Path"}
if (Test-Path "HKLM:$unistallPath" ) { Get-ChildItem "HKLM:$unistallPath" }
if (Test-Path "HKCU:$unistallWow6432Path") { Get-ChildItem "HKCU:$unistallWow6432Path"}
if (Test-Path "HKCU:$unistallPath" ) { Get-ChildItem "HKCU:$unistallPath" }
) |
ForEach-Object { Get-ItemProperty $_.PSPath } |
Where-Object {
$_.DisplayName -and !$_.SystemComponent -and !$_.ReleaseType -and !$_.ParentKeyName -and ($_.UninstallString -or $_.NoRemove)
} |
Sort-Object DisplayName | Select-Object -Property DisplayName, DisplayVersion, InstallDate | ft
}
}
Regex isn't great for parsing HTML; there can be a lot of odd scenarios; e.g. what happens if you have a node <td /> or <td colspan="2"> where you'd expected to have <td>? Equally, HTML (annoyingly) doesn't always follow XML rules; so an XML parser won't work (e.g. <hr> has no end tag / <hr /> is considered invalid).
As such, if parsing HTML you ideally need to use an HTML parser. For that, PowerShell has access to the HtmlFile com object, documented here: https://msdn.microsoft.com/en-us/library/aa752574(v=vs.85).aspx
Here are some examples...
This code finds all TR elements then strips all TDs after the first 4 and returns the row's outer HTML.
$html = #'
some sort of html code
<hr> an unclosed tab so it's messy like html / unlike xml
<table>
<tr><th>Program Name</th><th>version</th><th>install date</th><th>computer name</th><th>ID</th><th>Installed</th></tr>
<tr><td>Adobe Acrobat Reader DC</td><td>18.009.20050</td><td>20171130</td><td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td></tr>
<tr><td>Adobe Flash Player 28 ActiveX</td><td>28.0.0.137</td><td></td><td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td></tr>
<tr><td /><td>123</td><td></td><td>hello.com</td><td>456</td><td>True</td></tr>
</table>
etc...
'#
$Parser = New-Object -ComObject 'HTMLFile' #see https://msdn.microsoft.com/en-us/library/aa752574(v=vs.85).aspx
$Parser.IHTMLDocument2_write($html) #if you're using PS4 or below use instead: $Parser.IHTMLDocument2_write($html)
$parser.documentElement.getElementsByTagName('tr') | %{
$tr = $_
$tr.getElementsByTagName('td') | select-object -skip 4 | %{$tr.removeChild($_)} | out-null
$tr.OuterHtml
}
This works in a similar way; but just pulls back the values of the first 4 cells in each row:
$html = #'
some sort of html code
<hr> an unclosed tab so it's messy like html / unlike xml
<table>
<tr><th>Program Name</th><th>version</th><th>install date</th><th>computer name</th><th>ID</th><th>Installed</th></tr>
<tr><td>Adobe Acrobat Reader DC</td><td>18.009.20050</td><td>20171130</td><td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td></tr>
<tr><td>Adobe Flash Player 28 ActiveX</td><td>28.0.0.137</td><td></td><td>kratos.kcprod1.com</td><td>4104917a-93f2-46e5-941a-c4efd54504b7</td><td>True</td></tr>
<tr><td /><td>123</td><td></td><td>hello.com</td><td>456</td><td>True</td></tr>
</table>
etc...
'#
$Parser = New-Object -ComObject 'HTMLFile' #see https://msdn.microsoft.com/en-us/library/aa752574(v=vs.85).aspx
$Parser.IHTMLDocument2_write($html) #if you're using PS4 or below use instead: $Parser.IHTMLDocument2_write($html)
$parser.documentElement.getElementsByTagName('tr') | %{
$tr = $_
$a,$b,$c,$d = $tr.getElementsByTagName('td') | select-object -first 4 | %{"$($_.innerText)"} #we do this istead of `select -expand innerText` to ensure nulls are returned as blanks; not ignored
(New-Object -TypeName 'PSObject' -Property ([ordered]#{
AppName = $a
Version = $b
InstallDate = $c
ComputerName = $d
}))
}

powershell ConvertTo-Html add class

My script is pulling information from server, than it converts to HTML and send the report by email.
Snippet:
$sourceFile = "log.log"
$targetFile = "log.html"
$file = Get-Content $sourceFile
$fileLine = #()
foreach ($Line in $file) {
$MyObject = New-Object -TypeName PSObject
Add-Member -InputObject $MyObject -Type NoteProperty -Name Load -Value $Line
$fileLine += $MyObject
}
$fileLine | ConvertTo-Html -Property Load -head '<style> .tdclass{color:red;} </style>' | Out-File $target
Current HTML report snippet:
<table>
<colgroup><col/></colgroup>
<tr><th>Load on servers</th></tr>
<tr><td>Server1 load is 2442</td></tr>
<tr><td>Server2 load is 6126</td></tr>
<tr><td>Server3 load is 6443</td></tr>
<tr><td> </td></tr>
<tr><td>Higher than 4000:</td></tr>
<tr><td>6126</td></tr>
<tr><td>6443</td></tr>
</table>
This will generate an HTML report containing a table with tr and td.
Is there any method to make it generate td with classes, so I can insert the class name into the -head property with styles and make it red for the Higher than 4000: tds ?
I know this is in a old post, but stumbled cross it looking to do something similar.
I was able to add CSS styling by doing a replace.
Here is an example:
$Report = $Report -replace '<td>PASS</td>','<td class="GreenStatus">PASS ✔</td>'
You can then output $report to a file as normal, with the relevant css code in the header.
You would need some additional logic to find values over 4000
You can use the Get-Help ConvertTo-Html command you will get all parameters for the ConvertTo-Html command. Below is output:
ConvertTo-Html [[-Property] <Object[]>] [[-Head] <String[]>] [[-Title] <String>] [[-Body] <String[]>] [-As<String>] [-CssUri <Uri>] [-InputObject <PSObject>] [-PostContent <String[]>] [-PreContent <String[]>][<CommonParameters>]
You can create an external CSS file and give the CSS file path in the [-CssUri] parameter.

Reading from CSV produces duplicate entries in variable

I have this bit of code :
$servers = Import-Csv "sources.csv"
$computername = $servers.server
$ServiceName = $servers.services
sources.csv contains the following..
Server,Services
BRWS40,winrm
BRWS84,winrm
I have then a foreach, and the Write-Host is within that, it output this:
Write-Host "$computername - $ServiceName" -ForegroundColor black -BackgroundColor red
Output from above I get is:
BRWS40 BRWS84 - winrm winrm
Whereas I was wanting to have one computer and service per line.
BRWS40 - winrm
What am I doing wrong?
I amended the code from here.
$servers = Import-Csv "sources.csv" imports the content of sources.csv as a list of custom objects into the variable $servers.
$computername = $servers.server selects the value of the server property of each object into the variable $computername, thus generating a list of computer names.
$ServiceName = $servers.services selects the value of the services property of each object into the variable $ServiceName, thus generating a list of service names.
Note that $array.property will only work in PowerShell v3 and newer, because earlier versions don't automatically unroll the array to get the element properties, but try to access the property of the array object itself. If the array doesn't have such a property, the result will be $null, otherwise it will be the value of the property of the array. Either way it won't be what you want. To make the property expansion work across all PowerShell versions use Select-Object -Expand or echo the property in a ForEach-Object statement:
$computername = $servers | Select-Object -Expand server
$computername = $servers | ForEach-Object { $_.server }
When you put array variables in a string ("$computername - $ServiceName") the array elements are joined by the $OFS character (space by default), so "$computername" becomes BRWS40 BRWS84 and "$ServiceName" becomes winrm winrm.
To get the corresponding service name for each computer you need to process $servers in a loop, for instance:
foreach ($server in $servers) {
Write-Host ('{0} - {1}' -f $server.Server, $server.Services) ...
}
If you don't need a specific output format you could also use one of the Format-* cmdlets, for instance Format-Table:
Import-Csv "sources.csv" | Format-Table -AutoSize
You actually have to loop through your result:
$servers = Import-Csv "sources.csv"
$servers | %{
$computername = $_.server
$ServiceName = $_.services
write-host "$computername - $ServiceName" -foregroundcolor black -backgroundcolor red
}
or use the Format-Table cmdlet:
$servers | Format-Table

Replace NULL value in powershell output

I'm really struggling with what seems to be a simple thing. Any help is appreciated...
tldr; i'm trying to find and replace blank or NULL values from powershell
output to "No Data"
I am using the following powershell script to obtain installed application information
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
Below is sample output of the above script (filtered to my liking). I am taking this data, exporting it to CSV, in order to import into another application for analysis at a later time.
Host : Computer1
DisplayName : AutoDWG DWG DXF Converter 2015
Version :
Publisher :
InstallDate :
arrival_datetime : 2015-11-03T09:42:18
Host : Computer2
DisplayName : Beyond Compare Version 3.1.11
Version :
Publisher : Scooter Software
InstallDate : 20150218
arrival_datetime : 2015-11-03T09:42:18
the CSV export puts the data in the correct format, but where items have no data for version, publisher, etc..., it represents it as ",," (see below)
"Computer1","AutoDWG DWG DXF Converter 2015",,,,"2015-11-03T09:54:21"
"Computer2","Beyond Compare Version 3.1.11",,"Scooter Software","20150218","2015-11-03T09:54:21"
When importing the CSV, the blank values are re-interpreted as NULL, which throws the program for a loop, as it is expecting a string. I am attempting to change those to the string "No Data" but am having lots of trouble...
What would be the best way to handle this?
Using Select-Object would be your best bet. Just pipe the data to Select-Object, but customize each desired property as follows:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object Host, DisplayName, #{
Label = "Version"
Expression = { if ($_.Version) { $_.Version } else { "No Data" } }
}, Other Properties
You could inspect the property values as they are piped from the Import-Csv cmdlet, and change them. Example:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
ForEach-Object {
foreach ($p in $_.PSObject.Properties)
{
if ($p.Value -eq [string]::Empty)
{
$p.Value = 'No Data'
}
}
Write-Output $_
}
Building off of this answer, you could generate the calculated properties like this:
$SelectProperties = "Host","DisplayName"
$NoDataFields = "Version","Publisher","InstallDate"
$SelectProperties += $NoDataFields|ForEach-Object {
#{
Label = $_
Expression = [scriptblock]::Create("if(`$_.""$_""){ `$_.""$_"" } else { ""No Data"" }")
}
}
$Data = Import-Csv -Path "C:\SomePath.csv" |
Select-Object $SelectProperties
Do your columns have headers? This was how I did it, when I exported a CSV of groups and their owners. For any group that didn't have an owner in the "ManagedBy" column, it fills the field with "No Owner" instead of a blank space:
$CSVData = Import-Csv $TempCSV -Header "samAccountName", "ManagedBy"
$CSVData | %{
if($_.ManagedBy -eq "") {$_.ManagedBy="No Owner"}
}
$CSVData | Export-Csv $Filename -NoTypeInformation
You could just change the "ManagedBy" to your header name and the "$_.ManagedBy" to what you need, then obviously "No Owner" would be "No Data".
Another option that might work:
$TargetFile = "C:\temp\replaceNull.csv"
$File = Get-Content $TargetFile
$Output = #()
foreach ($Line in $File) {
$Output += $Line -replace ",,",",No Data,"
}
$Output | Set-Content $TargetFile

PowerShell match names with user email addresses and format as mailto

So i have the below script which scans a drive for folders, it then pulls in a csv with folder names and folder owners and then matches them and outputs to HTML.
I am looking for a way to within this use PS to look up the users names in the csv grab their email address from AD and then in the output of the HTML put them as mailto code.
function name($filename, $folderowners, $directory, $output){
$server = hostname
$date = Get-Date -format "dd-MMM-yyyy HH:mm"
$a = "<style>"
$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color:black;}"
$a = $a + "Table{background-color:#ffffff;border-collapse: collapse;}"
$a = $a + "TH{border-width:1px;padding:0px;border-style:solid;border-color:black;}"
$a = $a + "TR{border-width:1px;padding-left:5px;border-style:solid;border-
color:black;}"
$a = $a + "TD{border-width:1px;padding-left:5px;border-style:solid;border-color:black;}"
$a = $a + "body{ font-family:Calibri; font-size:11pt;}"
$a = $a + "</style>"
$c = " <br></br> Content"
$b = Import-Csv $folderowners
$mappings = #{}
$b | % { $mappings.Add($_.FolderName, $_.Owner) }
Get-ChildItem $directory | where {$_.PSIsContainer -eq $True} | select Name,
#{n="Owner";e={$mappings[$_.Name]}} | sort -property Name |
ConvertTo-Html -head $a -PostContent $c |
Out-File $output
}
name "gdrive" "\\server\location\gdrive.csv" "\\server\location$"
"\\server\location\gdrive.html"
Try adding something like this to the select:
#{n="email";e={"mailto:"+((Get-ADUser $mappings[$_.Name] -Properties mail).mail)}
You need to load the ActiveDirectory module before you can use the Get-ADUser cmdlet:
Import-Module ActiveDirectory
On server versions this module can be installed via Server Manager or dism. On client versions you have to install the Remote Server Administration Tools before you can add the module under "Programs and Features".
Edit: I would have expected ConvertTo-Html to automatically create clickable links from mailto:user#example.com URIs, but apparently it doesn't. Since ConvertTo-Html automatically encodes angular brackets as HTML entities and I haven't found a way to prevent that, you also can't just pre-create the property as an HTML snippet. Something like this should work, though:
ConvertTo-Html -head $a -PostContent $c | % {
$_ -replace '(mailto:)([^<]*)', '$2'
} | Out-File $output
Here's how I would do it (avoiding the use of the AD Module, only because it's not on all of my workstations and this works just the same), and assuming you know the user name already:
#Setup Connection to Active Directory
$de = [ADSI]"LDAP://example.org:389/OU=Users,dc=example,dc=org"
$sr = New-Object System.DirectoryServices.DirectorySearcher($de)
After I setup a connection to AD, I set my LDAP search filter. This takes standard ldap query syntax.
#Set Properties of Search
$sr.SearchScope = [System.DirectoryServices.SearchScope]"Subtree"
$sr.Filter = "(&(ObjectClass=user)(samaccountname=$Username))"
I then execute the search.
#Grab user's information from OU. If search returns nothing, they are not a user and the script exits.
$SearchResults = $sr.FindAll()
if($SearchResults.Count -gt 0){
$emailAddr = $SearchResults[0].Properties["mail"]
$mailto = "Contact User"
}
You can of course send the $mailto variable anywhere you want, and change it's html, but hopefully this gets you started.