{"id":54803,"date":"2008-11-27T11:28:00","date_gmt":"2008-11-27T11:28:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2008\/11\/27\/hey-scripting-guy-how-can-i-pull-server-diagnostic-information-and-have-it-saved-in-an-excel-spreadsheet-with-a-chart\/"},"modified":"2008-11-27T11:28:00","modified_gmt":"2008-11-27T11:28:00","slug":"hey-scripting-guy-how-can-i-pull-server-diagnostic-information-and-have-it-saved-in-an-excel-spreadsheet-with-a-chart","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/hey-scripting-guy-how-can-i-pull-server-diagnostic-information-and-have-it-saved-in-an-excel-spreadsheet-with-a-chart\/","title":{"rendered":"Hey, Scripting Guy! How Can I Pull Server Diagnostic Information and Have It Saved in an Excel Spreadsheet with a Chart?"},"content":{"rendered":"<h2><img decoding=\"async\" class=\"nearGraphic\" title=\"Hey, Scripting Guy! Question\" height=\"34\" alt=\"Hey, Scripting Guy! Question\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/q-for-powertip.jpg\" width=\"34\" align=\"left\" border=\"0\" \/> <\/h2>\n<p>Hey, Scripting Guy! Good morning. I would like to use WMI to get diagnostic information for my server. I would like to be able to do this remotely and to write the results to an Excel spreadsheet. When I am done, I would like the spreadsheet to also create a chart of the results. Is this asking too much? I have seen pieces of all this on the Script Center, but I have not found exactly what I am looking for. Help!<\/p>\n<p>&#8211; CC<\/p>\n<p><img decoding=\"async\" height=\"5\" alt=\"Spacer\" src=\"https:\/\/devblogs.microsoft.com\/scripting\/wp-content\/uploads\/sites\/29\/2019\/05\/spacer.gif\" width=\"5\" border=\"0\" \/><img decoding=\"async\" class=\"nearGraphic\" title=\"Hey, Scripting Guy! Answer\" height=\"34\" alt=\"Hey, Scripting Guy! Answer\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/a-for-powertip.jpg\" width=\"34\" align=\"left\" border=\"0\" \/> <\/p>\n<p>Hi CC,<\/p>\n<p>Guten morgen. Of course this can be done, but it will be a rather lengthy script. You do not believe me? Well, take a look at the <b>GetWmiPerformanceDataCreateExcelChart.ps1<\/b> script, which follows this paragraph. Even though we do not have this exact script written in VBScript, there are <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/resources\/officetips\/archive.mspx\" target=\"_blank\">a number of scripts<\/a> that talk about creating charts and working with Excel in the Script Center. There are also <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/resources\/qanda\/office.mspx\" target=\"_blank\">almost 100 \u201cHey, Scripting Guy!\u201d articles<\/a>\u2014nearly 4 months\u2019 worth of articles\u2014that specifically address VBScript and Excel. The concepts are the same. If you wish, you can migrate this script to VBScript. Use <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/topics\/winpsh\/convert\/default.mspx\" target=\"_blank\">this reference<\/a> for assistance if you need it. If you do decide to convert the script, why don&#8217;t you paste the script into the text of an e-mail message, send it to <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a> to share it with the <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/csc\/default.mspx\" target=\"_blank\">Community-Submitted Scripts Center<\/a>. If you need more information about performance monitoring with Windows PowerShell, refer to the <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/resources\/qanda\/all.mspx\" target=\"_blank\">scripts from earlier this week<\/a>.<\/p>\n<p>Here\u2019s the <b>GetWmiPerformanceDataCreateExcelChart.ps1<\/b>&nbsp;script:<\/p>\n<pre class=\"codeSample\">Function TestSheetName($sheetName)\n{\nif($sheetName.length -ge 30)\n {\n  $script:sheetName = $sheetName.substring(0,30)\n }\n} #end TestSheetName\nFunction CreateEmptyArray($ubound)\n{\n [int[]]$script:aryProp = [array]::CreateInstance(\"int\",$ubound)\n} #end CreateEmptyArray\nFunction GetWmiPerformanceData()\n{\n For($i = 0 ; $i -le $reps -1 ; $i++)\n  {\n   $aryProp[$i] +=([wmi]\"\\\\$computer\\root\\cimv2:$class.$key='$instance'\").$Property\n   Write-Progress -Activity \"Obtaining Processor info\" -Status \"% complete: \" `\n   -PercentComplete $i\n   Start-Sleep -Seconds $delay\n  } #end for\n}#end GetWmiPerformanceData\nFunction NewExcelSheet($sheetName)\n{\n $excel = new-object -comobject excel.application\n $excel.visible = $true\n $workbook = $excel.workbooks.add()\n $workbook.workSheets.item(3).delete()\n $workbook.WorkSheets.item(2).delete()\n $workbook.WorkSheets.item(1).Name = $sheetName\n $sheet = $workbook.WorkSheets.Item($sheetName)\n AddExcelContent\n} #end NewExcelSheet\nFunction AddExcelContent()\n{\n $x = 2\n $lineStyle = \"microsoft.office.interop.excel.xlLineStyle\" -as [type]\n $colorIndex = \"microsoft.office.interop.excel.xlColorIndex\" -as [type]\n $borderWeight = \"microsoft.office.interop.excel.xlBorderWeight\" -as [type]\n For($b = 1 ; $b -le 2 ; $b++)\n {\n  $sheet.cells.item(1,$b).font.bold = $true\n  $sheet.cells.item(1,$b).borders.LineStyle = $lineStyle::xlDashDot\n  $sheet.cells.item(1,$b).borders.ColorIndex =$colorIndex::xlColorIndexAutomatic\n  $sheet.cells.item(1,$b).borders.weight = $borderWeight::xlMedium\n }\n $sheet.cells.item(1,1) = \"CPU Time\"\n Foreach($item in $aryProp)\n {\n  $sheet.cells.item($x, 1) = $item\n  $x++\n } #end foreach\n NewExcelRange\n} #end AddExcelContent\nFunction NewExcelRange()\n{\n $range = $sheet.usedRange\n $range.EntireColumn.AutoFit() | Out-Null\n NewExcelChart\n} #end NewExcelRange\nFunction NewExcelChart()\n{\n $chartType = \"microsoft.office.interop.excel.xlChartType\" -as [type]\n $workbook.charts.add() | Out-Null\n $workbook.ActiveChart.chartType = $chartType::xl3DLine\n $workbook.ActiveChart.SetSourceData($range)\n $workbook.ActiveChart.HasTitle = $true\n $workbook.ActiveChart.ChartTitle.Text = \"$Class`n$Property\"\n SaveExcel\n} #end NewExcelChart\nFunction SaveExcel()\n {\n IF(Test-Path $path)\n   { \n    Remove-Item $path\n    $Excel.ActiveWorkbook.SaveAs($path)\n   }\n ELSE\n   {\n    $Excel.ActiveWorkbook.SaveAs($path)\n   }\n} #end SaveExcel\n# *** EntryPoint ***\n$computer = \".\"\n$delay = 1\n$reps = 10\n$Class = \"Win32_PerfFormattedData_PerfOS_Processor\"\n$key = \"name\"\n$instance = \"_Total\"\n$property = \"PercentProcessorTime\"\n$script:sheetName = $Class.substring(6)\nTestSheetName($script:sheetName)\n$Path=\"c:\\fso\\$sheetName.xls\"\nCreateEmptyArray($reps)\nGetWmiPerformanceData\nNewExcelSheet($sheetName) \n<\/pre>\n<p>The <b>GetWmiPerformanceDataCreateExcelChart.ps1<\/b> script is organized into a series of functions. These functions help to make it easier to read, as well as easier to re-use the code. This technique also makes it easier to add additional functionality to the script later or to troubleshoot the script if something does not work properly.<\/p>\n<p>The script begins with the <b>TestSheetName<\/b> function. This function is required because Microsoft Office Excel will not allow a worksheet name that is more than 31 characters in length. However, we have no such restriction for WMI class name lengths. Because the WMI class name is used to create the sheet name, it is necessary to be able to trim the length of the name if it is too long. To do this, we use the <b>length<\/b> property to see if it is greater than or equal to 30 characters. If it is, we use the <b>substring<\/b> method to grab 30 characters beginning at position 0. This is seen&nbsp;here:<\/p>\n<pre class=\"codeSample\">Function TestSheetName($sheetName)\n{\nif($sheetName.length -ge 30)\n {\n  $script:sheetName = $sheetName.substring(0,30)\n }\n} #end TestSheetName\n<\/pre>\n<p>The next function we come to is the <b>CreateEmptyArray<\/b> function. This is used to create an empty function to hold the data from the WMI query. If you wanted to capture multiple data sets, you could create multiple arrays here. To create the empty array we use the static method <b>CreateInstance<\/b> from the <b>system.array<\/b> class. We give it the data type it will hold and the upper boundary. This is stored in a script level variable as seen&nbsp;here:<\/p>\n<pre class=\"codeSample\">Function CreateEmptyArray($ubound)\n{\n [int[]]$script:aryProp = [array]::CreateInstance(\"int\",$ubound)\n} #end CreateEmptyArray\n<\/pre>\n<p>The next thing we need to do is gather the WMI performance information. To do this, we use a <b>for<\/b> loop to control how many times we query for the information. The <b>[WMI]<\/b> type accelerator is used to facilitate direct connection to a specific instance of the WMI data. Everything in the connection string is abstracted into variables that control the way the query is generated. The computer name comes from the <b>$computer<\/b> variable and the class name is from the <b>$class<\/b> variable. The key value for the class and the specific instance of the performance counter are similarly supplied. By putting the entire query into a set of parentheses, we force the line to be parsed inside the parentheses first, and then we grab the value of the property we are interested in and write it to our array. This line of code is seen here:<\/p>\n<pre class=\"codeSample\">$aryProp[$i] +=([wmi]\"\\\\$computer\\root\\cimv2:$class.$key='$instance'\").$Property<\/pre>\n<p>The next thing we do is use the <b>Write-Progress<\/b> cmdlet to display a progress indicator bar while we are gathering the information (for more information on <b>Write-Progress<\/b>, see <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/resources\/pstips\/mar08\/pstip0321.mspx\" target=\"_blank\">this article<\/a>):<\/p>\n<pre class=\"codeSample\">Function GetWmiPerformanceData()\n{\n For($i = 0 ; $i -le $reps -1 ; $i++)\n  {\n   $aryProp[$i] +=([wmi]\"\\\\$computer\\root\\cimv2:$class.$key='$instance'\").$Property\n   Write-Progress -Activity \"Obtaining Processor info\" -Status \"% complete: \" `\n   -PercentComplete $i\n   Start-Sleep -Seconds $delay\n  } #end for\n}#end GetWmiPerformanceData\n<\/pre>\n<p>Now we need to create a Microsoft Excel spreadsheet. The code to create the spreadsheet is in the <b>NewExcelSheet<\/b> function. As a best practice, I try to use the <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/topics\/winpsh\/cmdline_std.mspx#EWHAC\" target=\"_blank\">verb\/noun pattern<\/a> that was established by the Windows PowerShell team. We first need to create an instance of the <b>Excel.Application<\/b> object. We do this by using the <b>new-object<\/b> cmdlet. Next, we make the spreadsheet visible by setting the <b>visible<\/b> property to true. After we have a blank Excel application, we need to add a workbook to it. We do this by using the <b>workbooks.add<\/b> method. We then delete the two extra spreadsheets that are not needed, rename the spreadsheet, and then use the <b>item<\/b> method from the <b>worksheets<\/b> collection to return a single worksheet. We then call the <b>addExcelContent<\/b> function. This code is seen&nbsp;here:<\/p>\n<pre class=\"codeSample\">Function NewExcelSheet($sheetName)\n{\n $excel = new-object -comobject excel.application\n $excel.visible = $true\n $workbook = $excel.workbooks.add()\n $workbook.workSheets.item(3).delete()\n $workbook.WorkSheets.item(2).delete()\n $workbook.WorkSheets.item(1).Name = $sheetName\n $sheet = $workbook.WorkSheets.Item($sheetName)\n AddExcelContent\n} #end NewExcelSheet\n<\/pre>\n<p>The <b>AddExcelContent<\/b> function is used to add content to the Excel spreadsheet. The first thing we do is create some <b>type<\/b> enumerations that will govern the way the borders, color, and weight of the Excel spreadsheet. This information is all <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb149081.aspx\" target=\"_blank\">documented in MSDN<\/a>. After we have created the enumerations, we use them to configure the first two rows in the spreadsheet. This is seen here:<\/p>\n<pre class=\"codeSample\">$x = 2\n $lineStyle = \"microsoft.office.interop.excel.xlLineStyle\" -as [type]\n $colorIndex = \"microsoft.office.interop.excel.xlColorIndex\" -as [type]\n $borderWeight = \"microsoft.office.interop.excel.xlBorderWeight\" -as [type]\n\n For($b = 1 ; $b -le 2 ; $b++)\n {\n  $sheet.cells.item(1,$b).font.bold = $true\n  $sheet.cells.item(1,$b).borders.LineStyle = $lineStyle::xlDashDot\n  $sheet.cells.item(1,$b).borders.ColorIndex =$colorIndex::xlColorIndexAutomatic\n  $sheet.cells.item(1,$b).borders.weight = $borderWeight::xlMedium\n }\n<\/pre>\n<p>Now we add our column heading and enter the data that is stored in the <b>$aryProp<\/b> array. To add the column heading we use the <b>(1,1)<\/b> syntax. This writes to the first cell in the first row. We then use a <b>foreach<\/b> loop to go through the elements of the array and write to subsequent rows in the spreadsheet. This is shown&nbsp;here:<\/p>\n<pre class=\"codeSample\">$sheet.cells.item(1,1) = \"CPU Time\"\n Foreach($item in $aryProp)\n {\n  $sheet.cells.item($x, 1) = $item\n  $x++\n } #end foreach\n<\/pre>\n<p>The entire <b>AddExcelContent<\/b> function is shown&nbsp;here:<\/p>\n<pre class=\"codeSample\">Function AddExcelContent()\n{\n $x = 2\n $lineStyle = \"microsoft.office.interop.excel.xlLineStyle\" -as [type]\n $colorIndex = \"microsoft.office.interop.excel.xlColorIndex\" -as [type]\n $borderWeight = \"microsoft.office.interop.excel.xlBorderWeight\" -as [type]\n\n For($b = 1 ; $b -le 2 ; $b++)\n {\n  $sheet.cells.item(1,$b).font.bold = $true\n  $sheet.cells.item(1,$b).borders.LineStyle = $lineStyle::xlDashDot\n  $sheet.cells.item(1,$b).borders.ColorIndex =$colorIndex::xlColorIndexAutomatic\n  $sheet.cells.item(1,$b).borders.weight = $borderWeight::xlMedium\n }\n \n $sheet.cells.item(1,1) = \"CPU Time\"\n\n Foreach($item in $aryProp)\n {\n  $sheet.cells.item($x, 1) = $item\n  $x++\n } #end foreach\n NewExcelRange\n} #end AddExcelContent\n<\/pre>\n<p>At this point, we have an Excel spreadsheet with data from the WMI performance query written into the first column. We also have a column head and a dot-dash style border around the first two cells:<\/p>\n<p><img decoding=\"async\" height=\"380\" alt=\"Image of the spreadsheet with data\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/hey1127\/HSG_Perf4_01.jpg\" width=\"500\" border=\"0\" \/> <\/p>\n<p>&nbsp;<\/p>\n<p>After we have created the spreadsheet, we need to create a range. The range will be used to create the chart in the next step. To create the range, we use the <b>usedrange<\/b> property from the <b>worksheet<\/b> object. We then use the <b>autofit<\/b> method to size the range to the number of cells that contain data. This is a nice feature. The <b>NewExcelRange<\/b> function is seen&nbsp;here:<\/p>\n<pre class=\"codeSample\">Function NewExcelRange()\n{\n $range = $sheet.usedRange\n $range.EntireColumn.AutoFit() | Out-Null\n NewExcelChart\n} #end NewExcelRange\n<\/pre>\n<p>It is now time to create the chart. The Excel <b>chart<\/b> object is very rich, and you can waste a lot of time trying to create the perfect Excel chart. Here we create a basic three-dimensional line chart with the title that comes from the WMI class and the property we selected. The <b>range<\/b> object is used to set the data source for the chart. This is, I have found, the easiest way to add the data to the Excel chart. The <b>NewExcelChart<\/b> function is seen&nbsp;here:<\/p>\n<pre class=\"codeSample\">Function NewExcelChart()\n{\n $chartType = \"microsoft.office.interop.excel.xlChartType\" -as [type]\n $workbook.charts.add() | Out-Null\n $workbook.ActiveChart.chartType = $chartType::xl3DLine\n $workbook.ActiveChart.SetSourceData($range)\n $workbook.ActiveChart.HasTitle = $true\n $workbook.ActiveChart.ChartTitle.Text = \"$Class`n$Property\"\n SaveExcel\n} #end NewExcelChart\n<\/pre>\n<p>The completed chart is shown here: <\/p>\n<p><img decoding=\"async\" height=\"380\" alt=\"Image of the completed chart\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/hey1127\/HSG_Perf4_02.jpg\" width=\"500\" border=\"0\" \/> <\/p>\n<p>&nbsp;<\/p>\n<p>We now want to save our work. To do this, we first see if an old version of the Excel spreadsheet exists. If it does, we delete it, and then create a new one by using the <b>SaveAs<\/b> method from the <b>workbook<\/b> object. This is seen in the <b>SaveExcel<\/b> function shown here:<\/p>\n<pre class=\"codeSample\">Function SaveExcel()\n {\n IF(Test-Path $path)\n   { \n    Remove-Item $path\n    $Excel.ActiveWorkbook.SaveAs($path)\n   }\n ELSE\n   {\n    $Excel.ActiveWorkbook.SaveAs($path)\n   }\n} #end SaveExcel\n<\/pre>\n<p>That is all the functions. To set everything up and to set everything in motion, we use the following code that initializes the variables, checks the spreadsheet name, and then calls the functions in the appropriate order. This is seen&nbsp;here:<\/p>\n<pre class=\"codeSample\">$computer = \".\"\n$delay = 1\n$reps = 10\n$Class = \"Win32_PerfFormattedData_PerfOS_Processor\"\n$key = \"name\"\n$instance = \"_Total\"\n$property = \"PercentProcessorTime\"\n\n$script:sheetName = $Class.substring(6)\nTestSheetName($script:sheetName)\n$Path=\"c:\\fso\\$sheetName.xlsx\"\n\nCreateEmptyArray($reps)\nGetWmiPerformanceData\nNewExcelSheet($sheetName) \n<\/pre>\n<p>Well, CC, this has been a long answer to a short question. But I hope you were able to stay with it. Keep in mind that we will be having an Excel week coming up toward the end of December. There is also a TechNet magazine article on the way with some more Excel automation. I kind of jumped the gun a little because it fit in well with your question and with the performance monitoring we have been doing. The advantage of the function approach to building the script is that once you have particular functions, you just stick them together. Have an awesome day, and I will see you tomorrow for Quick-Hits Friday.<\/p>\n<p><font class=\"Apple-style-span\" face=\"Verdana\" size=\"3\"><span class=\"Apple-style-span\"><b><b>Ed Wilson and Craig Liebendorfer, Scripting Guys<\/b><\/b><\/span><\/font><\/p>\n<p><font class=\"Apple-style-span\" face=\"Verdana\" size=\"3\"><b><\/b><\/font><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey, Scripting Guy! Good morning. I would like to use WMI to get diagnostic information for my server. I would like to be able to do this remotely and to write the results to an Excel spreadsheet. When I am done, I would like the spreadsheet to also create a chart of the results. Is [&hellip;]<\/p>\n","protected":false},"author":595,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[710,48,41,49,31,60,3,4,45,6],"class_list":["post-54803","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-excel-spreadsheet","tag-microsoft-excel","tag-monitoring","tag-office","tag-operating-system","tag-performance","tag-scripting-guy","tag-scripting-techniques","tag-windows-powershell","tag-wmi"],"acf":[],"blog_post_summary":"<p>Hey, Scripting Guy! Good morning. I would like to use WMI to get diagnostic information for my server. I would like to be able to do this remotely and to write the results to an Excel spreadsheet. When I am done, I would like the spreadsheet to also create a chart of the results. Is [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/54803","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/595"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=54803"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/54803\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=54803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=54803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=54803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}