Showing posts with label WMI. Show all posts
Showing posts with label WMI. Show all posts

Saturday, August 11, 2012

PowerShell: Use VBScript to Color Table Rows

I decided to take a break from the XML solutions for HTML management of multi-table reports in PowerShell.  This week I will demonstrate an extremely simple method that most Admin scripters can master because it uses the old standby “VBScript” to do the color tagging in the browser.

We use the same method to generate a table and create the HTML:

Param(
$reportpath
}
Get-WmiObject win32_logicaldisk |
Select-Object @{N='Server';E={$_.__SERVER}},
Deviceid,
@{N='PercentFree';E={[math]::Round($_.Freespace/$_.Size * 100,0)}} | ConvertTo-Html | Out-File $reportpath
&$reportpath

Same report.  Same HTML.

We can colorize the rows very easily by injecting some VBScript into the output file.

$head=@"
<script language="
vbscript">
Sub window_onload()
Set tbl1 = document.getElementsByTagName("
table")(0)
For Each r In tbl1.Rows
Set cell = r.lastChild
if IsNumeric(cell.innerText) Then
If cell.innerText < $threshold Then
cell.style.color="
red"
Else
cell.style.color="
green"
End If
Else
If LCase(cell.tagName) <> "
th" Then
cell.innerText="
n/a"
End If
End If
Next
End Sub
</script>
"
@

The above is just a “here-string”  wrapped around a block of VBScript.  You can put the script into the output file manually to see it work.  I built and tested the script in an HTA in about 5 minutes.

The VBScript is just a “window_onload” event that grabs first the table on the page and goes through each row in a for loop.

Set tbl1 = document.getElementsByTagName("table")(0)
For Each r In tbl1.Rows
Set cell = r.lastChild
If IsNumeric(cell.innerText) Then
If cell.innerText < 10 Then
cell.style.color="red"
Else
cell.style.color="green"
End If
Else
If LCase(cell.tagName) <> "th" Then
cell.innerText="n/a"
End If
End If
Next

Note how we skip the header cells and how I test for the existence of a numeric value.  For each row I get the last child of the row which I know is the column with the numbers. All columns are tested against a value that tells us when to color the column red.  We could also color the whole row by using cell.parent.style=”red”.  We might even set the background color.

Here is a copy of the test file: Copy and run it to see that it works.

<html>
<head>
<script language="vbscript">
   1:  
   2:     Sub window_onload()
   3:         Set tbl1 = document.getElementsByTagName("table")(0)
   4:         For Each r In tbl1.Rows
   5:             Set cell = r.lastChild 
   6:             if IsNumeric(cell.innerText) Then
   7:                 If cell.innerText < 10 Then
   8:                     cell.style.color="red"
   9:                 Else
  10:                     cell.style.color="green"
  11:                 End If
  12:             Else
  13:                 If LCase(cell.tagName) <> "th" Then
  14:                     cell.innerText="n/a"
  15:                 End If
  16:             End If
  17:         Next
  18:     End Sub
</script>
</head>
<body>
<table>
<colgroup>
<col/>
<col/>
<col/>
</colgroup>
<tr><th>Server</th><th>Deviceid</th><th>PercentFree</th></tr>
<tr><td>OMEGA2</td><td>A:</td><td></td></tr>
<tr><td>OMEGA2</td><td>C:</td><td>2</td></tr>
<tr><td>OMEGA2</td><td>D:</td><td></td></tr>
<tr><td>OMEGA2</td><td>E:</td><td>87</td></tr>
<tr><td>OMEGA2</td><td>F:</td><td>89</td></tr>
<tr><td>OMEGA2</td><td>X:</td><td>60</td></tr>
</table>
</body>
</html>

Once I had this working I just copied the script tag to my PowerShell editor and wrapped it in a here-string.  I also added a variable in place of the hard coded match variable.

Combining all the pieces we get this very simple PowerShell script.

<#
Create-DrivespaceReport.ps1
#>
Param(
$threshold = 50,
$reportpath='\\omega2\test\DriveSpace.htm'
)

$head=@"
<style type="
text/css">
body{
background-color: antiquewhite;
}
h4{
font-style: italic;
}
table{
border-collapse:collapse;
background-color: LightGoldenRodYellow;
}
table td{
border: 1px solid black;
font-weight: thinner;
}
table th{

border: 1px solid black;
font-weight: bolder;
color: #00008B;
background-color: gold;
}
</style>
<script language="
vbscript">
Sub window_onload()
Set tbl1 = document.getElementsByTagName("
table")(0)
For Each r In tbl1.Rows
Set cell = r.lastChild
if IsNumeric(cell.innerText) Then
If cell.innerText < $threshold Then
cell.style.color="
red"
Else
cell.style.color="
green"
End If
Else
If LCase(cell.tagName) <> "
th" Then
cell.innerText="
n/a"
End If
End If
Next
End Sub
</script>
"
@
$precontent="<h4>Disk Usage Report for $([datetime]::today.ToLongDateString())</h4>"
# generate the table
gwmi win32_logicaldisk |
Select-Object @{N='Server';E={$_.__SERVER}},
Deviceid,
@{N='PercentFree';E={[math]::Round($_.Freespace/$_.Size * 100,0)}} |
ConvertTo-Html -head $head -PreContent $precontent | Out-File $reportpath

# page has been copied to a trusted share so the script will execute
&$reportpath


For the final version I threw in a simple style sheet along with the script to give the page some color.  Click the link below to see the final sample.
 
Here is a link to download it: Simple Dynamically Colored Table Script

Here is a link to a sample of the report: Sample Report

Other articles in the series on managing HTML reports from PowerShell.

PowerShell: Dynamically Color PosH Generated HTML:Part 1
PowerShell: Dynamically Color PosH Generated HTML:Part 2
PowerShell: Dynamically Color PosH Generated HTML:Part 3

Monday, July 23, 2012

PowerShell: Dynamically Color PosH Generated HTML:Part 2

PowerShell: Dynamically Color PosH Generated HTML:Part 1

PowerShell: Dynamically Color PosH Generated HTML:Part 2
PowerShell: Dynamically Color PosH Generated HTML:Part 3

In the first part of this article I discussed using CSS styles, table ids and classes to colorize individual tables.  I ended with a question about how we might be able to colorize individual rows of a table in a way that would show up in an email message.

How about coloring individual lines?  Can we use this to set a lines color depending on the value of a column? Of course.  Next time I will show how easy it is to do that although I have already given you almost all of the pieces for doing it.  We just need about three more lines of code and some knowledge about how XML works and how to make HTML do some of the work for us.

Here is the code that will colorize the individual rows based on a columns value in that row.

If you remember from before we loaded the HTML fragment into an XML DOM for manipulation.  We will use the same DOM to find and colorize the rows.

$rows=$xml.table.selectNodes('//tr')
for($i=1;$i -lt $rows.count; $i++){
$value=$rows.Item($i).LastChild.'#text'
if($value.Length -gt 0 -and [int]$value -le $threshhold){
Write-Host "Candidate found - setting color to red at $value" -fore green
$attr=$xml.CreateAttribute('style')
$attr.Value='background-color: red;'
[void]$rows.Item($i).Attributes.Append($attr)
}
}

As you can see we are just adding an attribute to the row.

First we find all of the row objects which are represented by the ‘tr’ tag:

$rows=$xml.table.selectNodes('//tr')

This is the XPath that will return all rows of a table.  Next I enumerate the rows but skip the first row as it is the header. I use an explicit ‘for’ loop because we need the index to return the row object. The index is $i and is incremented in the ‘for’ statement.  The index allows us to dereference the ‘#text’ node of the ‘last child’ which is the end column of the row.

$value=$rows.Item($i).LastChild.'#text'

Now that I have the value of the column it is time to test the value against our ‘threshold’.  We have to cast it to an integer for this to work correctly. We also need to filter out all nodes that may be blank so I check for zero length.

if($value.Length -gt 0 -and [int]$value -le $threshhold){

Once we find a column that meets our criteria we can add our attributes and set it as a color style:  This is done by creating an attribute named ‘style’ and assigning its value to be ‘background-color: red;’  just like we would type it into the HTML tag

$attr=$xml.CreateAttribute('style')
$attr.Value='background-color: red;'

There we have successfully edited HTML without playing with strings again.  Creating and modifying HTML this way prevents breaking the HTML because the XML DOM will never generate broken XML or broken XHTML.

Send Email Table With Rows Colored to Show Alerts
$threshhold=10
$html=gwmi win32_logicaldisk |
Select-Object __SERVER,deviceid,@{N='PercentFree';E={[math]::Round($_.Freespace/$_.Size * 100,0)}} |
ConvertTo-Html -Fragment
$xml=[xml]$html
$rows=$xml.table.selectNodes('//tr')
for($i=1;$i -lt $rows.count; $i++){
$value=$rows.Item($i).LastChild.'#text'
if($value.Length -gt 0 -and [int]$value -le $threshhold){
Write-Host "Candidate found - setting color to red at $value" -fore green
$attr=$xml.CreateAttribute('style')
$attr.Value='background-color: red;'
[void]$rows.Item($i).Attributes.Append($attr)
}
}
$html=$xml.OuterXml|Out-String
$style='<style type=text/css>#diskTbl { background-color: blue; }</style>'
$body=ConvertTo-Html -head $style -body $html -Title "Disk Usage Report"|Out-String
$msg=@{
To=$to
From=$from
Subject="Disk usage report for $([datetime]::Now)"
BodyAsHTML=$true
Body=$body
}

This is what the email looks like:

image

Of course we can do more but look at what has been accomplished in only a couple of lines.  We can use theses two techniques to add more tables all colorized and alerted or highlighted in any manner we choose and all without playing with strings. We can be as fancy as we like now because the data is being stuffed into html fragments for us and we can then decorate the data in a very controlled fashion.

What is next?

Next time I will attempt to combine these techniques into a web page with multiple tables and FTP it to a public web site.  I will also show how to add external style sheets and how to inject VBScript or JavaScript into the pages generated by PowerShell.  Maybe we can even look at PowerShell generated ASP.NET pages.

Until next time….

Part 1: http://tech-comments.blogspot.com/2012/07/powershell-dynamically-color-posh.html

Sunday, June 14, 2009

Scripting: Win32_Service – Change Account/Password

When using Win32_Service to change the service account and/or service account password the following needs to be understood.

StartName is an account name and not the name of the service which is Name for internal service short name and DisplayName for the full visible service name.  The account name MUST be specified when changing the password as it appears that both are always validated.  The only time a Null or empty password can be used is when setting the account to LocalService.  All unused arguments in VB or VBScript must be set to Null or you will get an error. 

Note that the account used must be an accessible account either local or AD and must have the "run as a service" permission or you will get an service account not valid error.  The password must be present but does not need to be correct as this is only checked on an account restart.  Restating the service will check the  password and use the new setting.  If you are just setting passwords then you will have a race condition.

It is more normal to create a new account with the new password and reset all services to the new account.  This will guarantee that you DO NOT have a race condition when setting multiple accounts as the old account can keep the old password until all services have been re-provisioned.

uploads/2491/WMIChangeService.vbs.txt

1 Function WMIChangeServiceAccount(sComputer,serviceName, account, password ) 2 Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & sComputer & "\root\cimv2") 3 Set colServices = wmi.ExecQuery("Select * From Win32_Service Where Name = '" & serviceName & "'") 4 For Each oService in colServices 5 result = oService.Change(Null ,Null ,Null ,Null ,Null ,Null ,account , password) 6 If result <> 0 Then 7 Select Case result 8 Case 0 : msg = "Success" 9 Case 1 : msg = "Not Supported" 10 Case 2 : msg = "Access Denied" 11 Case 3 : msg = "Dependent Services Running" 12 Case 4 : msg = "Invalid Service Control" 13 Case 5 : msg = "Service Cannot Accept Control" 14 Case 6 : msg = "Service Not Active" 15 Case 7 : msg = "Service Request Timeout" 16 Case 8 : msg = "Unknown Failure" 17 Case 9 : msg = "Path Not Found" 18 Case 10 : msg = "Service Already Running" 19 Case 11 : msg = "Service Database Locked" 20 Case 12 : msg = "Service Dependency Deleted" 21 Case 13 : msg = "Service Dependency Failure" 22 Case 14 : msg = "Service Disabled" 23 Case 15 : msg = "Service Logon Failure" 24 Case 16 : msg = "Service Marked For Deletion" 25 Case 17 : msg = "Service No Thread" 26 Case 18 : msg = "Status Circular Dependency" 27 Case 19 : msg = "Status Duplicate Name" 28 Case 20 : msg = "Status Invalid Name" 29 Case 21 : msg = "Status Invalid Parameter" 30 Case 22 : msg = "Status Invalid Service Account" 31 Case 23 : msg = "Status Service Exists" 32 Case 24 : msg = "Service Already Paused" 33 Case Else : msg = "Not defined" 34 End Select 35 Wscript.StdErr.WriteLine "WMIChangeServiceAccount failed:" & msg 36 End If 37 Next 38 End Function 39 40 WMIChangeServiceAccount ".", "Browser", "domain\account", "newpassword" 41 42

"Browser" is the name of the service.  The display name is "Computer Browser".  These names are not interchangeable.  "Name" required the internal short name of the service.  You can change the code to use "DisplayName" as the filter.

PowerShell is much better for WMI as it can let us view elements quickly at the command line.

gwmi win32_service
will get all of the installed services
gwmi win32_service | select name
will give us all internal service names
gwmi win32_service -filter "name like 'b%'"
will filter all names that have "b" as a first letter.

Try this with vbscript of CMD.  It can be done but takes far longer to accomplish.

Here is the same password change coded in PowerShell:

gwmi win32_service -filter "name like 'bro%'"|%{$_.Change($null,$null,$null,$null,$null,$null,$null,"domain\account","newpw")}

The decode of the result could be added as an "Enum" extension to the PowerShell type system and we could also add a script to the class that would allow a method of "ChangePassword" that would just take a password.