{"id":2088,"date":"2015-09-14T00:00:00","date_gmt":"2015-09-14T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/build-your-own-resource-monitor-in-a-jiffy\/"},"modified":"2016-07-28T10:48:02","modified_gmt":"2016-07-28T10:48:02","slug":"build-your-own-resource-monitor-in-a-jiffy","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/build-your-own-resource-monitor-in-a-jiffy\/","title":{"rendered":"Build Your Own Resource Monitor in a Jiffy"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">Often times, as developers, we want to exercise some code that has side effects  on external resources-be it database transactions, file creation, file content updates, event log records, process  state, memory use, and so on. We want to see what effect our code is having by keeping track of those external resources  in real-time, or close to it. There are a variety of dedicated tools for some of these types of things. Some give you an  instant in time: tools like Windows&#8217; Event Viewer snapshots your event log, and SQL Server Management Studio lets you  snapshot any table (or group of tables with a join). You can refresh that snapshot as you wish, in order to monitor  those resources. Others provide a continuous monitor: SQL Profiler or SysInternals&#8217; Process Explorer \/ Process Monitor  are noteworthy examples. These tools are of a type that continually refreshes the snapshot, effectively giving you  near-real-time feedback.<\/p>\n<p class=\"MsoNormal\">And if there happens to be a dedicated monitoring program of that type for what  you need to track, that&#8217;s great, but that isn&#8217;t always the case. Often you want to track something unique to your code  that doesn&#8217;t expose a WMI performance counter, so you need a custom monitor. <\/p>\n<p class=\"MsoNormal\">We&#8217;ve all probably used PowerShell&#8217;s <code>Get-Process<\/code> cmdlet to get repeated  information about a process that you&#8217;re tracking, but what about something a bit easier and slicker. I felt sure that  someone in the PowerShell community would have come up with a simple solution. Then I discovered an old PowerShell  routine by Marc van Orsouw,&#160; &#8216;thePowerShellGuy&#8217;, a.k.a MoW; &#160;a Swiss Cloud consultant and a former PowerShell MVP best known for his WMI  Explorer script. &#160;This script was a small and lightweight PowerShell utility  designed to do display dynamically changing information in a grid from a repeating PowerShell script block.<\/p>\n<p class=\"MsoNormal\">All you needed to do was:<\/p>\n<ul>\n<li>&#160;figure out how to collect what you want in PowerShell (and PowerShell is rich enough and expressive  \t\tenough to allow you to grab data about most anything on your computer) <\/li>\n<li>&#160;feed it to the script to generate a monitor for continual update and display.<\/li>\n<\/ul>\n<p class=\"MsoNormal\">It was on an old website which <a href=\"http:\/\/thepowershellguy.com\/blogs\/posh\/archive\/2007\/01\/21\/powershell-gui-scripblock-monitor-script.aspx\">no  longer exists<\/a>. I found a copy of the very same code on <a href=\"https:\/\/code.google.com\/p\/mod-posh\/source\/browse\/powershell\/playground\/Start-Monitor.ps1?r=409\">Google Code<\/a>  though that site is apparently being phased out as well. <\/p>\n<p class=\"MsoNormal\">That original script provided just the barebones, but it worked well-enough to  immediately prove its worth to a Windows developer such as I.&#160; Over time, I  added a large number of features, bug fixes, and enhancements to make it more useful and more user-friendly, but I do  want to express my appreciation to ThePowerShellGuy, because without that first script proving its worth, I&#8217;d never have  had the impetus or inclination to gradually morph it into the MonitorFactory. <\/p>\n<p class=\"MsoNormal\">In this article, I&#8217;ll explain how to use the MonitorFactory and provide some  illustrations of when it would be handy. If you&#8217;d like to run the code, grab the open-source code from Github <a href=\"https:\/\/github.com\/msorens\/MonitorFactory\">here<\/a>.<\/p>\n<h2>Getting Started With Monitor Factory<\/h2>\n<p class=\"MsoNormal\">Monitor Factory allows you to create a specific watcher or tracker for some  metric that is returned from a script block of PowerShell yielding tabular data. The API for Monitor Factory consists of  just one cmdlet, <code>Start-Monitor<\/code>. Before creating a monitor with <code>Start-Monitor<\/code>, you just need to create a recipe in the form of a PowerShell  script block.<\/p>\n<p class=\"MsoNormal\">As PowerShell&#8217;s own help illustrates (see <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/hh847893.aspx\">about_script_blocks<\/a> in PowerShell help), you can  run <code>Invoke-Command<\/code> to execute a script block, as in:<\/p>\n<pre>\t&#160;&#160;&#160; Invoke-Command -ScriptBlock { Get-Process }\n<\/pre>\n<p class=\"MsoNormal\">&#8230; which is exactly the same as if you had just invoked <code>Get-Process<\/code> by itself, producing a list of processes on your computer. <code>Start-Monitor<\/code> is very similar but with two crucial differences. This again invokes <code>Get-Process<\/code>&#8230;<\/p>\n<pre>\t&#160;&#160;&#160; Start-Monitor -ScriptBlock { Get-Process }\n<\/pre>\n<p class=\"MsoNormal\">&#8230; but instead, it sends the output to an interactive grid and then refreshes the  data&#160; ad infinitum at whatever time interval you specify.<\/p>\n<h2>Some Practical Applications<\/h2>\n<p class=\"MsoNormal\">Before getting into all the nitty-gritty of using Monitor Factory, let&#8217;s take a  look at some real-world examples of what you might want to use it for. These are arranged in order of increasing  complexity of use to give you some idea of the possibilities you can use Monitor Factory for, though truly the sky is  the limit!<\/p>\n<h3>Database Monitoring<\/h3>\n<p class=\"MsoNormal\">SQL data is a natural for Monitor Factory. In SQL Server Management Studio you  can write queries of arbitrary complexity which ultimately yield a data table, just what makes Monitor Factory salivate!  To move your SQL code from SSMS over to the command-line, <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/cc281720(v=sql.110).aspx\">Invoke-Sqlcmd<\/a> will let you execute  any arbitrary SQL query from the PowerShell command line, displaying the tabular output on your console. (It is the  PowerShell equivalent of the <code>sqlcmd<\/code> utility.) Wrap that whole thing in a  script block and feed it to Monitor Factory to take that console output into an interactive data grid window, updated  automatically as frequently as you like. This is very handy to watch what your database layer is doing as you develop a  new application. Here&#8217;s an example showing about the shortest, useful bit of SQL you could write, reporting on the free  space of your database logs. From the SQL output I use standard PowerShell to select just four properties of interest:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Start-Monitor -AsJob { \n&#160;&#160;&#160; Invoke-Sqlcmd 'DBCC SQLPERF(logspace)' | \n&#160;&#160;&#160; Select-Object 'Database Name','Log Size (MB)','Log Space Used (%)',HasErrors \n} \n<\/pre>\n<p class=\"MsoNormal\"> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2283-img8.jpg\" alt=\"2283-img8.jpg\" \/><\/p>\n<p class=\"MsoNormal\">OK, that was definitely a very short query-but not terribly interesting. Here&#8217;s  one with a bit more panache, generating a list of the top twenty longest-running queries in the specified database.  Generate a monitor for this and you can see every few seconds if there are any new potential performance hogs. You can,  of course, paste this code in as a literal, just as the previous query did, but due to its length it makes more sense to  store this in a file; let&#8217;s call it TopTwentyQueries.sql. (As one useful variation, note the penultimate line is  commented out; uncommenting that will yield the longest queries in just the last 20 minutes.)<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">SELECT TOP 20\n&#160;&#160;&#160; last_execution_time AS [Last Executed]\n&#160;&#160;&#160; ,convert(DECIMAL(8,3),\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; convert(DECIMAL, last_elapsed_time)\/1000000)\n&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160; AS [Last Time Taken(secs)]\n&#160;&#160;&#160; ,convert(DECIMAL(8,3),\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; convert(DECIMAL, total_elapsed_time\/execution_count) \/ 1000000)\n&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160; AS [Average Time(Secs)]\n&#160;&#160;&#160; , execution_count AS [Execution Count]\n&#160;&#160;&#160; ,SUBSTRING(st.text, (qs.statement_start_offset\/2) + 1,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ((CASE statement_end_offset\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHEN -1 THEN DATALENGTH(st.text)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE qs.statement_end_offset END\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; - qs.statement_start_offset)\/2) + 1) AS [SQL Statement]\nFROM sys.dm_exec_query_stats AS qs\nCROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st\n-- WHERE last_execution_time &gt;dateadd(MINUTE,-20,getdate())\nORDER BY total_elapsed_time \/ execution_count DESC;\n<\/pre>\n<p class=\"MsoNormal\">Then you can generate a monitor like so:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Start-Monitor { \n&#160;&#160;&#160; Invoke-Sqlcmd -InputFile TopTwentyQueries.sql\n} -AsJob -Interval 5sec -DisplayName 'Longest Running Queries'\n&#160;\n<\/pre>\n<p class=\"illustration\"> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2283-imgC.jpg\" alt=\"2283-imgC.jpg\" \/><\/p>\n<p class=\"MsoNormal\">With the query safely ensconced in a file, you can readily see that this could  be genericized to a &#8220;SQL Monitor Factory&#8221; (though you do not yet know the semantic or syntactic elements). Here is one  possible implementation of such a function, taking in five parameters. It builds a parameter list for <code>Start-Monitor<\/code> using those parameters. Note that the function allows the title  to be optional, so it only sets the <code>DisplayName<\/code> parameter if you provide it.  You will learn more about <code>Start-Monitor<\/code>&#8216;s calling semantics shortly.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">function Start-SqlMonitor (\n&#160;&#160;&#160; [string]$path,\n&#160;&#160;&#160; [string]$server,\n&#160;&#160;&#160; [string]$database,\n&#160;&#160;&#160; [string]$title = '',\n&#160;&#160;&#160; [string]$interval = '5sec'\n)\n{\n&#160;&#160;&#160; $monitorParams = @{\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; AsJob = $true\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; Interval = $interval\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ScriptBlock = {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Invoke-Sqlcmd -InputFile $args[0] -Server $args[1] -Database $args[2]\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ArgumentList = $path, $server, $database\n&#160;&#160;&#160; }\n&#160;&#160;&#160; if ($title) { $monitorParams.DisplayName = $title }\n&#160;&#160;&#160; Start-Monitor @monitorParams\n} \n<\/pre>\n<p class=\"MsoNormal\">Here is the simplified equivalent call you can now make with the previous query:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Start-SqlMonitorTopTwentyQueries.sql localhostmaster 'Longest Running Queries'&#160; <\/pre>\n<h3>File System Monitoring<\/h3>\n<p class=\"MsoNormal\">Windows Management Instrumentation (WMI) is an API that provides a rich source  of data on your local machine or even on remote machines. <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/hh849824.aspx\">Get-WmiObject<\/a> is the PowerShell API to WMI  (superseded by <a href=\"https:\/\/technet.microsoft.com\/library\/jj590758(v=wps.630).aspx\">Get-CimInstance<\/a> in  PowerShell v5.0). This bit of code lets you keep a watchful eye on your disk usage. <code>Get-PSProvider<\/code> returns the drive letters of the disks on the local machine.  That list is fed into <code>Get-WmiObject<\/code> to fetch details about each disk, and  from that output we again select just the properties of interest, some of which are <i>calculated properties<\/i> that return more useful values than just the raw data.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Start-Monitor -AsJob {\n&#160;&#160;&#160; (Get-PSProvider FileSystem).Drives.Name | % {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; Get-WmiObject Win32_LogicalDisk `\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -ComputerName localhost -Filter \"DeviceID='$_`:'\" |\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; Select-Object @{n='Drive'; e={$_.name}},\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FileSystem,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @{n='Size (GB)';e={($_.size\/1gb).ToString('F2')}},\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @{n='Free Space (GB)';e={($_.freespace\/1gb).ToString('F2')}},\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @{n='% Free'; e={(($_.Freespace\/$_.size)*100).ToString('F2')}}\n&#160;&#160;&#160; }\n} \n&#160;\n\n<\/pre>\n<p class=\"illustration\"> <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2283-img13.jpg\" alt=\"2283-img13.jpg\" \/><\/p>\n<h3>Source Control Monitoring<\/h3>\n<p class=\"MsoNormal\">If you are working with colleagues on a project it is often quite handy to have  a handle on the &#8220;pulse&#8221; of your source control activity. As long as your source control system has a command-line  interface, you can often massage that output to display new commits. I am using TFS for my current job, so all I need to  do is invoke this wrapper function that shows me the number of files that are <i> stale<\/i> (i.e. there are files freshly committed by my colleagues that I have not yet brought into my workspace),  grouped by folder.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Start-StaleMonitorfolder  C:\\MyBase\\TFS\\root\\Extensions<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2283-img18.jpg\" alt=\"2283-img18.jpg\" \/><\/p>\n<p class=\"MsoNormal\"> \tMy wrapper function<code>Start-StaleMonitor<\/code> can also display a more condensed summary <i>by  project<\/i> or an expanded, fully itemized list <i>by file<\/i>. Observe in the code  below how it simply calls <code>Start-Monitor<\/code> with one of three cmdlets: <code>Get-StaleTfsFiles<\/code>, <code>Get-StaleTfsFolders<\/code>,  or <code>Get-StaleTfsProjects<\/code>. It also supplies a few more parameters to <code>Start-Monitor<\/code> that will be discussed shortly, though you can likely discern  most of what it does. <\/p>\n<pre class=\"lang:ps theme:powershell-ise\">function Start-StaleMonitor(\n&#160;&#160;&#160; [ValidateSet('file','folder','project')]$choice = 'file',\n&#160;&#160;&#160; [string]$path = 'C:\\MyBase\\TFS\\root\\Main',\n&#160;&#160;&#160; [string]$interval = '5m'\n)\n{\n&#160;&#160;&#160; switch($choice) {\n&#160;&#160;&#160;&#160;&#160; \"file\" { \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Start-Monitor -AsJob { \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Add-PSSnapin Microsoft.TeamFoundation.PowerShell; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Get-StaleTfsFiles $args[0] }&#160;&#160;&#160; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -Interval $interval \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -DisplayName \"Stale TFS Files [$path]\" \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -ArgumentList $path\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160; \"folder\" { \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;Start-Monitor -AsJob { \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Add-PSSnapin Microsoft.TeamFoundation.PowerShell; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Get-StaleTfsFolders $args[0] }&#160; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -Interval $interval \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -DisplayName \"Stale TFS Folders [$path]\" \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -ArgumentList $path\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160; \"project\" { \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Start-Monitor -AsJob { \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Add-PSSnapin Microsoft.TeamFoundation.PowerShell; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Get-StaleTfsProjects $args[0] } \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -Interval $interval \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -DisplayName \"Stale TFS Projects [$path]\" \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; -ArgumentList $path\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160; }\n}\n<\/pre>\n<p> When I said &#8220;all you need to do&#8230;&#8221; at the beginning of this section, that remark  was facetious (at least for TFS). First, I had to write <code>Start-StaleMonitor<\/code>.  Then I had to write <code>Get-StaleTfsFiles<\/code>, <code>Get-StaleTfsFolders<\/code>, and <code>Get-StaleTfsProjects<\/code>. (All of those are  available from my <a href=\"http:\/\/cleancode.sourceforge.net\/wwwdoc\/APIbookshelf.html\">open-source PowerShell library<\/a>,  by the way.) Not done yet&#8230; then you have to have the <a href=\"http:\/\/bit.ly\/1voD4HK\">TFS 2013 PowerTools<\/a> extension  installed in Visual Studio, which also installs some PowerShell cmdlets that are needed within those three cmdlets  (hence the <code>Add-PSSnapin<\/code> you see in the code above). It might seem like a lot  of work, but I find the information I get from <code>Start-StaleMonitor<\/code> very useful  in my daily work. And if you use TFS, <code>you<\/code> now have a plug-and-play solution; you just have to install  it!<\/p>\n<h2>Considerations for Script Blocks<\/h2>\n<h3>Be Aware of Multiple Object Types<\/h3>\n<p class=\"MsoNormal\">Unlike <code>Invoke-Command<\/code>, mentioned in  the introduction, which just outputs to the console, there are some considerations to be aware of when constructing your  script block for <code>Start-Monitor<\/code>.&#160;  Let&#8217;s use <code>Get-ChildItem<\/code> to illustrate these.<\/p>\n<pre>\t&#160;&#160;&#160; Start-Monitor -ScriptBlock { Get-ChildItem -Recurse }\n<\/pre>\n<p class=\"MsoNormal\">This monitors all files and directories rooted at the current directory,  flattening the result into a single list of objects.&#160; But what kind of  objects?&#160; <code>Get-ChildItem<\/code> emits both <code>FileInfo<\/code> and <code>DirectoryInfo<\/code>  objects, each with different properties.&#160; The interactive grid that is  generated, however, can only handle a single object type; it determines its columns from the <i>first<\/i> object it encounters.&#160; For <code>Get-ChildItem<\/code> this will typically be a <code>DirectoryInfo<\/code> object so all the column headers will correspond to <code>DirectoryInfo<\/code> properties.&#160; <code>FileInfo<\/code> objects share a lot of the same properties, but not all; files will thus have some columns shown as  empty and some file properties will not appear at all.<\/p>\n<p class=\"MsoNormal\">Adding a filter to restrict the output to ignore directories&#8230;<\/p>\n<pre>\t&#160;&#160;&#160;&#160;&#160; Start-Monitor { Get-Child -File -Recurse }\n<\/pre>\n<p class=\"MsoNormal\">&#8230; will allow you to see <i>all<\/i> the  properties of each file, because all objects going into the grid are now the same type, <code>FileInfo<\/code>.<\/p>\n<h3>Be Aware of the Properties of Your Output<\/h3>\n<p class=\"MsoNormal\">You will immediately notice that the grid display is quite different from what  you get on the console when you just run <code>Get-ChildItem<\/code>.&#160; I&#8217;m not speaking just of the formatting. Rather, you will see a lot more columns: the grid has around 20 columns  whereas on the console you only see four (Mode, LastWriteTime, Length, and Name) !<\/p>\n<p class=\"MsoNormal\">This difference is due to the fact that PowerShell defines a set of default  properties for a cmdlet that it uses when it outputs to the console.&#160; But  that is merely an output rendering artifact. All the properties of the object are really there. And they have to be:  otherwise, it would not make any sense that when you pipe to another cmdlet you would only get a portion of an object!  So for<code>Start-Monitor<\/code>, your script block&#8217;s output does not go to the console.  Rather it is consumed by an internal function that constructs a <code>DataTable<\/code>  object that in turn is connected to the grid view to render it as output. But the constructor function sees the entire  object; in the case of <code>Get-ChildItem<\/code>, that means all of its 20 or so  properties.&#160; Therefore, if you want to get the same output that you would on  the console, you have to explicitly select just those properties (here I am switching from <code>Get-ChildItem<\/code> to its <code>ls<\/code> alias for brevity):<\/p>\n<pre>\t&#160;&#160;&#160;&#160;&#160; Start-Monitor { ls -File -R | select Name,Mode,LastWriteTime,Length }\n<\/pre>\n<h3>Foreground and Background Execution<\/h3>\n<p class=\"MsoNormal\"><code>Start-Monitor<\/code>  can be run as a foreground or background process.&#160; Just add <code>-AsJob<\/code> to run as a background process. Internally there are a lot of  machinations needed to support the asynchronous version but from the user perspective there are only two things to be  aware of.<\/p>\n<p class=\"MsoNormal\">Foreground or background, Start-Monitor will pop open a secondary window. But  run synchronously your PowerShell host window will be tied up running the application-you will not get a prompt to  continue doing other work. That&#8217;s really the main reason for making a cmdlet that supports background processing, so  that you can continue executing other commands in the same window. <\/p>\n<p class=\"MsoNormal\">The second reason is less obvious but more important. When you run synchronous  applications you get error messages on your console; with asynchronous applications you do not, because once launched  they are no longer tied to the console. Instead, output is collected in the background job itself. A job is quite a  powerful PowerShell entity. It persists for the life of your PowerShell session unless you explicitly remove it. Thus,  even if you close the window opened by <code>Start-Monitor<\/code>, its job will still be  available. Which is a good thing, because you can request its output (with <code>Receive-Job<\/code>) to examine any errors reported. <code>Receive-Job<\/code> needs a job ID  (which you can get from <code>Get-Job<\/code>) or you can receive output from all jobs by  just using an asterisk (*) for the job ID. The point here, is that when you are crafting a new script block to feed to <code>Start-Monitor<\/code> it is a good idea to run it in the foreground first. But if at  some point you are mysteriously getting no output in your data grid and you are running as a background job, you just  have to remember that there may be errors that you are not seeing, and you need but ask for them with <code>Receive-Job<\/code>.<\/p>\n<h3>Using Variables<\/h3>\n<p class=\"MsoNormal\">The samples you have seen thus far use script blocks whose contents are <i>static<\/i>, i.e. they are akin to literals rather than variables. What if you  wanted to make a convenience wrapper to monitor the contents of a directory you specify at runtime? Consider first the  synchronous (foreground) version:<\/p>\n<pre>\tfunction MonitorDir($myDir)\n\t{\n\t&#160;&#160;&#160;&#160;&#160; Start-Monitor { ls $myDir -R }\n\t}\n<\/pre>\n<p class=\"MsoNormal\">That will work fine; the <code>$myDir<\/code>  variable is accessible because the script block is not wrapped inside a background job. As soon as you change it to a  background job, though&#8230;<\/p>\n<pre>\tfunction MonitorDir($myDir)\n\t{\n\t&#160;&#160;&#160;&#160;&#160; Start-Monitor { ls $myDir -R } -AsJob\n\t}\n<\/pre>\n<p class=\"MsoNormal\">&#8230; you will get an empty grid. If you then query the data from the job (&#8220;<code>Receive-Job  *<\/code>&#8221; is an easy way to not bother with the ID if you only have one outstanding job) you will see an error saying the  variable has not been set.&#160; To get around this issue you have to &#8220;manually&#8221;  supply any arguments you wish to inject into the script block using the <code>$ArgumentList<\/code> parameter:<\/p>\n<pre>\tfunction MonitorDir($myDir)\n\t{\n\t&#160;&#160;&#160;&#160;&#160; Start-Monitor { ls $args[0] -R } -AsJob -ArgumentList $myDir\n\t}\n<\/pre>\n<p class=\"MsoNormal\">The <code>-ArgumentList<\/code> is a list, as it  intimates by the name, though here it is just used with a single value.&#160;  Index into the list as shown above (<code>$args[<\/code><i>x<\/i><code>]<\/code>)  to retrieve the supplied values.<\/p>\n<h3>Using Modules<\/h3>\n<p class=\"MsoNormal\">When you need to use an external module within your script block you must  explictly bring it in scope <i>inside<\/i> the script block; it will <i>not<\/i> be inherited from your current scope. Depending on the module this will  require an <code>Add-PSSnapin<\/code> call or an <code>Import-Module<\/code> call.<\/p>\n<h2>User Interface<\/h2>\n<p class=\"MsoNormal\">Rounding out this article, these last couple sections are a mini-reference,  detailing both the <i>input<\/i> (parameters you can specify to <code>Start-Monitor<\/code>), and the <i>output<\/i> (components of the window generated  by <code>Start-Monitor<\/code>).<\/p>\n<p class=\"MsoNormal\">Here&#8217;s an example of a monitor created by Monitor Factory:<\/p>\n<p class=\"illustration\"> \t<img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2283-img1A.jpg\" alt=\"2283-img1A.jpg\" \/><\/p>\n<ol>\n<li>The <code>ScriptBlock<\/code> parameter that you supply is displayed in the window&#8217;s title bar (truncated to fit if too long) <i>unless<\/i> you specify a more user-friendly name with the <code>DisplayName<\/code> parameter.<\/li>\n<li>The <code>ScriptBlock<\/code> parameter is also displayed in the bottom status bar, again truncated to fit. Here, since space  is limited, any leading <code>Import-Module<\/code> or <code>Add-PSSnapin<\/code> commands are suppressed.&#160; <\/li>\n<li>If you hover over the value in the status bar, however, you can see  the entire <code>ScriptBlock<\/code> in a tooltip. If it is a lengthy command, then line-breaks are added to the tooltip as shown to  help ensure the whole script block is easily visible.<\/li>\n<li>The countdown timer shows you when the next refresh will occur. If the  time-to-refresh is less than a minute, the countdown timer refreshes every second.&#160; Otherwise, it refreshes every 5 seconds. Normally grey, if you pause the countdown via the <code>Action<\/code> menu (or Ctrl+P), the background turns salmon, as shown in the figure.  Besides the regular refresh interval, you can refresh on-demand from the <code>Action<\/code>  menu (or Ctrl+R). Any manual refreshes will <i>not<\/i> alter the next auto-refresh  time.<\/li>\n<li>When you create a monitor you can optionally specify a refresh  interval (which defaults to 10 seconds). You can specify the interval in seconds, minutes, or hours. The status bar  reflects your interval but normalizes it to seconds, minutes, or hours, regardless of what unit you supplied. (Example,  if you specify 300 seconds it displays &#8220;5 minutes&#8221;.)<\/li>\n<li>On each refresh, the row count is automatically updated to reflect the  refreshed content of the grid. Sometimes when you are viewing the grid it is handy to know what a prior value was for a  row or a cell. Under the <code>Action<\/code> menu you can select <code>Prior Data<\/code> (or Ctrl+D) to switch context to the prior data set. When you do,  the row indicator turns yellow as shown. Press Ctrl+D again to return to the current data set (or just wait for the next  refresh).<\/li>\n<\/ol>\n<h2>Syntax<\/h2>\n<p class=\"MsoNormal\">The API for Monitor Factory includes just these few parameters:<\/p>\n<pre>\tStart-Monitor\n&#160;&#160;&#160;&#160;&#160; &#160;[[-ScriptBlock] &lt;ScriptBlock&gt;]\n&#160;&#160;&#160;&#160;&#160; &#160;[[-Interval] &lt;String&gt;]\n&#160;&#160;&#160;&#160;&#160; &#160;[[-DisplayName] &lt;String&gt;]\n&#160;&#160;&#160;&#160;&#160; &#160;[[-ArgumentList] &lt;Object[]&gt;]\n&#160;&#160;&#160;&#160;&#160; &#160;[-AsJob]\n<\/pre>\n<div class=\"indent\">\n<h3>ScriptBlock<\/h3>\n<ul>\n<li>\n<p class=\"MsoNormal\"><i>Default:<\/i> \t\t{ Get-Process | Select-Object PSResources,cpu }<\/p>\n<\/li>\n<\/ul>\n<p class=\"MsoNormal\">The sequence of commands to execute.&#160;  You may specify a single command, a command pipeline, or multiple commands.&#160;  (If you are entering all on one line, separate multiple commands with semicolons.) Be sure to include <code>Import-Module<\/code> and <code>Add-PSSnapin<\/code>  commands as needed to find your commands!<\/p>\n<h3>Interval<\/h3>\n<ul>\n<li>\n<p class=\"MsoNormal\"><i> \t\tDefault unit:<\/i>  \t\tseconds<\/p>\n<\/li>\n<li>\n<p class=\"MsoNormal\"><i>Default value:<\/i> \t\t10<\/p>\n<\/li>\n<\/ul>\n<p class=\"MsoNormal\">The time between successive data refreshes. This can either be a simple integer  or an integer with units of <code>hours<\/code>, <code>minutes<\/code>, or <code>seconds<\/code>, or abbreviations thereof. Note that <i>no<\/i> spaces are allowed between the number and the units, so any of these are  valid: 10,  10min,  10h, or  10seconds.<\/p>\n<h3>DisplayName<\/h3>\n<p class=\"MsoNormal\">An optional string to display in the window title bar rather than the raw  command sequence. If not supplied, the <code>ScriptBlock<\/code> parameter is used.<\/p>\n<h3>ArgumentList<\/h3>\n<p class=\"MsoNormal\">This specifies the arguments (parameter values) for the script that is specified  by the <code>ScriptBlock<\/code> parameter. This mechanism is required if you create a background monitor (with <code>-AsJob<\/code>) and you wish to supply external variables into your <code>ScriptBlock<\/code>.<\/p>\n<h3>AsJob<\/h3>\n<p class=\"MsoNormal\">Runs the monitor as a background task, freeing up your PowerShell host to  receive other inputs from you. If you get a blank grid or otherwise unexpected output, check the job details for  possible errors. (Look up the last job id with <code>Get-Job<\/code> or, if you do not have  other active jobs you can just do &#8220;<code>Receive-Job *<\/code>&#8221; to get the output of all jobs.) Alternately, rerun <code>Start-Monitor<\/code> <i>without<\/i> the <code>-AsJob<\/code> parameter to show any errors or message  directly in the console. One limitation of background jobs: you cannot copy text from the data grid. Rerun <code>Start-Monitor<\/code> without <code>-AsJob<\/code> if  you need to copy data out of the grid.<\/p>\n<\/div>\n<h2>Summary<\/h2>\n<p class=\"MsoNormal\">MonitorFactory provides a small, clean framework for those frequent occasions  when you need to keep an eye on some external resources as you are examining behavior or performance of your code. It  lets you whip up an auto-refreshing &#8220;watch window&#8221; on-the-fly to customize exactly what you need to see. Because  sometimes, the answer is obvious when you can see how the data changes.<\/p>\n<p class=\"MsoNormal\">Grab the open-source code from Github <a href=\"https:\/\/github.com\/msorens\/MonitorFactory\">here<\/a>. For documentation (besides this article!) the cmdlet help  is fully instrumented, so you can invoke <code>Get-Help Start-Monitor<\/code> to see all  the details. Also I will be adding a bit on the Github wiki itself as time allows.<\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s great to be able to monitor a counter or any other changing metric while engaged in development work. You&#8217;d think that the two alternatives would be using a third-party tool or hacking a PowerShell script.  Well no, because there could be an existing open-source PowerShell module that would do it for you, and with a little customization could give you precisely what you need.&hellip;<\/p>\n","protected":false},"author":221868,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[35],"tags":[4635,4179,4871],"coauthors":[],"class_list":["post-2088","post","type-post","status-publish","format-standard","hentry","category-powershell","tag-powershell","tag-source-control","tag-sysadmin"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2088","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221868"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=2088"}],"version-history":[{"count":5,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2088\/revisions"}],"predecessor-version":[{"id":41225,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2088\/revisions\/41225"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=2088"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=2088"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=2088"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=2088"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}