{"id":1167,"date":"2011-07-05T00:00:00","date_gmt":"2011-07-05T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/powershell-eventing-and-sql-server-restores\/"},"modified":"2021-08-24T13:40:22","modified_gmt":"2021-08-24T13:40:22","slug":"powershell-eventing-and-sql-server-restores","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/powershell-eventing-and-sql-server-restores\/","title":{"rendered":"PowerShell Eventing and SQL Server Restores"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">It all began one bright morning, when my good friend and PowerShell Jedi Ravikanth Chaganti (<a href=\"http:\/\/www.ravichaganti.com\/\">blog<\/a>| <a href=\"http:\/\/twitter.com\/#!\/ravikanth\">twitter<\/a>) \u00a0asked me if I had a PowerShell script \u00a0to restore databases. This sounded like a pretty simple process, and so I told him that what he needed was available on <a href=\"http:\/\/sqlpsx.codeplex.com\/\">CodePlex in the form of SQLPSX<\/a>. However, it turned out the challenge he faced was not so simple, and he elaborated on his real problem:<\/p>\n<p>He actually needed to restore 50 databases in asynchronous mode and, having discovered that the Restore class had events, wanted to use those to trigger a message when the restore process finished.<\/p>\n<p>Now <i>this<\/i> sounded interesting! But how to do it? Helloooo PowerShell Eventing&#8230;<\/p>\n<h2>PowerShell Eventing<\/h2>\n<p>Eventing is a feature built into PowerShell V2 which lets you respond to the asynchronous notifications that many objects support (as seen on the <a href=\"http:\/\/blogs.msdn.com\/b\/powershell\/archive\/2008\/06\/11\/powershell-eventing-quickstart.aspx\">Windows PowerShell Blog<\/a>). However, my goal is not to explain what the PowerShell Eventing \u00a0feature is; I&#8217;m here to demonstrate how to implement an effective real-world solution <i>using<\/i> that feature.<\/p>\n<p class=\"pullout\">Of course, if you want to know more about what PowerShell Eventing is, then I suggest you read the links at the end of the article.<\/p>\n<p>Before we get started, I&#8217;ll explain that I modified Chad Miller&#8217;s (<a href=\"http:\/\/sev17.com\/\">blog<\/a>|<a href=\"http:\/\/twitter.com\/#!\/cmille19\">twitter<\/a>) original <b>Invoke-Sqlrestore<\/b> function to use the complete Restore event for our purposes (with Chad&#8217;s kind permission, naturally). In the course of the article, I&#8217;ll show you step by step how I got the final solution, and you can download the finished script from the bottom of the article. The altered function is called <b>Invoke-SqlrestoreEventing<\/b> , inside the <b>PoshTest.Psm1<\/b> module, and comes with additional SMO assemblies to import it directly into your\u00a0 PowerShell user profile.<\/p>\n<h2>The Problem<\/h2>\n<p>I needed an automated and reasonably scalable way to restore 50 databases asynchronously, and be notified when each one was finished.<\/p>\n<h3>Step 1 &#8211; Just Show a Message<\/h3>\n<p>My first step towards Eldorado was to just show a &#8220;Restore Completed&#8221; message when a restore operation was finished. If we take a look at the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/microsoft.sqlserver.management.smo.restore.aspx\">MSDN information for the Restore Class<\/a>, we find the available Events, including <b>Complete<\/b>:<i><\/i><\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-1.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image002.jpg\" alt=\"1319-clip_image002.jpg\" width=\"567\" height=\"130\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 1 &#8211; The available events on the Restore Class (click to enlarge)<\/b><\/p>\n<p>So I wrote some PowerShell to use that:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$restore = new-object (\"Microsoft.SqlServer.Management.Smo.Restore\")  Register-ObjectEvent -InputObject $restore\u00a0 -EventName \"Complete\" -SourceIdentifier CompleteRestore -Action { Write-Host \"Restore Completed\"}\u00a0 | Out-Null\r\n<\/pre>\n<p>And tested it to make sure it works:<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-2.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image004.jpg\" alt=\"1319-clip_image004.jpg\" width=\"567\" height=\"70\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 2 &#8211; Our initial script, working fine. (click to enlarge)<\/b><\/p>\n<p>That all looked OK. So imagine my surprise when I tried to restore again, and saw this:<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-3.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image006.jpg\" alt=\"1319-clip_image006.jpg\" width=\"566\" height=\"174\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 3 &#8211; The same simple message script, but something&#8217;s gone wrong. (click to enlarge)<\/b><\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">Cannot subscribe to event. A subscriber with source identifier 'CompleteRestore' already exists.\r\n<\/pre>\n<p>\u00a0I realized I had created a <b>CompleteRestore<\/b> subscriber in the <b>SourceIdentifier<\/b> parameter of the <b>Register-ObjectEvent<\/b> cmdlet, so I needed to <i>unregister<\/i> it before I could run the cmdlet again:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">try { $restore.SqlRestore($server) }\r\ncatch {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 blablablabla\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n} finally {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Unregister-Event CompleteRestore\r\n}\r\n<\/pre>\n<h3>Second Step &#8211; Running in an Asynchronous PowerShell Job<\/h3>\n<p>With my message script running smoothly, my second thought was &#8220;<i>Neat, but it&#8217;s not much good without being asynchronous<\/i>&#8220;. If I have to restore 50 databases, it cannot be in a serialized way! So I tried :<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$server = \"Vader\"\r\n$dbname = \"TestPoshEventing_6\"\r\n$filepath = \"c:\\temp\\backup\\TestPoshEventing.bak\"\r\n$Realocatefiles = @{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_6.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_6.ldf'}\r\nStart-Job -Name \"Restore1\" -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1 -Force} -scriptblock { Invoke-SqlRestoreEventing -sqlserver $args[0] -dbname $args[1]\u00a0 -filepath $args[2] -relocatefiles $args[3] -force } -ArgumentList $server, $Dbname ,$filepath ,$Realocatefiles\r\n<\/pre>\n<p>Aaand&#8230;it didn&#8217;t work. Why not? Because background Jobs run in a different runspace, and so anything we send to output in the console won&#8217;t show up. To work around that, I needed to use <b>Receive-Job<\/b> :<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$server = \"Vader\"\r\n$dbname = \"TestPoshEventing_6\"\r\n$filepath = \"c:\\temp\\backup\\TestPoshEventing.bak\"\r\n$Realocatefiles = @{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_6.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_6.ldf'}\r\n$job = Start-Job -Name \"Restore1\" -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1 -Force} -scriptblock { Invoke-SqlRestoreEventing -sqlserver $args[0] -dbname $args[1]\u00a0 -filepath $args[2] -relocatefiles $args[3] -force } -ArgumentList $server, $Dbname ,$filepath ,$Realocatefiles\r\n\u00a0\r\nWait-Job $job | Receive-Job\u00a0\r\n<\/pre>\n<p>And the Oscar goes to&#8230; PowerShell! Everything now works just fine.<\/p>\n<h3>Third Step -Showing a Message and the Database Name<\/h3>\n<p>The &#8220;Restore Completed&#8221; message I put together earlier is handy, but not actually that useful without knowing which database has been restored. To improve that, I added the <b>$dbname<\/b> element:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">Invoke-SqlRestoreEventing -sqlserver Vader -dbname \"TestPoshEventing_6\"\u00a0 -filepath \"c:\\temp\\backup\\TestPoshEventing.bak\" -relocatefiles @{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_6.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_6.ldf'} -force\r\nTestPoshEventing_6 Restore Completed\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-4.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image008.jpg\" alt=\"1319-clip_image008.jpg\" width=\"567\" height=\"167\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 4 &#8211; The &#8220;Restore Complete&#8221; message, complete with the database name. (click to enlarge)<\/b><\/p>\n<p>Now you should hopefully be thinking, as I was, that because backgrounds jobs run in a different\u00a0 runspace, <b>$dbname<\/b> will not be displayed when we put these two scripts together. How do we solve this?<\/p>\n<p>\u00a0Never fear! In this case, I used the <b>-messagedata<\/b> parameter on <b>Register- ObjectEvent<\/b>, and got the value we need using <b>$event.Messagedata<\/b>:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">\u00a0Register-ObjectEvent -InputObject $restore\u00a0 -EventName \"Complete\" -SourceIdentifier CompleteRestore -Action { Write-Host \"$($event.MessageData) restore Completed\"} -MessageData $dbname | Out-Null\r\n<\/pre>\n<p>Now let&#8217;s run the function:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$server = \"Vader\"\r\n$dbname = \"TestPoshEventing_6\"\r\n$filepath = \"c:\\temp\\backup\\TestPoshEventing.bak\"\r\n$Realocatefiles = @{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_6.mdf'; TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_6.ldf'}\r\n$job = \r\nStart-Job\u00a0 -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1 -Force} -scriptblock { Invoke-SqlRestoreEventing -sqlserver $args[0] -dbname $args[1]\u00a0 -filepath $args[2] -relocatefiles $args[3] -force } -ArgumentList $server, $Dbname ,$filepath ,$Realocatefiles\r\nWait-Job $job | Receive-Job \r\n<\/pre>\n<p>&#8230; and watch the magic happening:<\/p>\n<p class=\"illustration\">\u00a0 <a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-5.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image010.jpg\" alt=\"1319-clip_image010.jpg\" width=\"567\" height=\"133\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 5 &#8211; Asynchronous database restores, complete with &#8220;Restore Complete&#8221; messages for each database. (click to enlarge)<\/b><\/p>\n<h2>Scaling out The code<\/h2>\n<p>One of the main reasons why I use PowerShell is because of its inherent capacity to manage multiple servers with just a few lines of script. That is, scaling out my code is relatively easy. Which is just as well, because while the solution as it stands is fine for a test case, it&#8217;s not quite ready to deal with 50 databases efficiently.<\/p>\n<p>\u00a0The first thing I needed to do was to add the server name into the message so that I knew exactly which database was being managed at each stage. For this I used <b>-messagedata<\/b> again, but with a twist: I passed the parameters as a property to <b>PSObject<\/b> and used <b>$Event.MessageData.&lt;ProPertyName&gt;<\/b><\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$pso = new-object psobject -property @{Server=$server;DbName=$dbname} \r\nRegister-ObjectEvent -InputObject $restore\u00a0 -EventName \"Complete\" -SourceIdentifier CompleteRestore -Action { Write-Host \"Server $($event.MessageData.Server), database $($event.MessageData.dbname) restore Completed\"} -MessageData $pso | Out-Null\r\n<\/pre>\n<p>And with that in place, let&#8217;s see how this code deals with restoring 2 databases:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">\u00a0$server = \"Vader\"\r\n$dbname = \"TestPoshEventing_20\"\r\n$filepath = \"c:\\temp\\backup\\TestPoshEventing.bak\"\r\n$Realocatefiles = @{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_20.mdf'; TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_20.ldf'}\r\n$job = Start-Job\u00a0 -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1 -Force} -scriptblock { Invoke-SqlRestoreEventing -sqlserver $args[0] -dbname $args[1]\u00a0 -filepath $args[2] -relocatefiles $args[3] -force } -ArgumentList $server, $Dbname ,$filepath ,$Realocatefiles\r\n\u00a0\r\n$server = \"Vader\"\r\n$dbname = \"TestPoshEventing_21\"\r\n$filepath = \"c:\\temp\\backup\\TestPoshEventing.bak\"\r\n$Realocatefiles = @{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_21.mdf'; TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_21.ldf'}\r\n\u00a0\r\n$job1 = Start-Job -Name \"Restore2\" -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1 -Force} -scriptblock { Invoke-SqlRestoreEventing -sqlserver $args[0] -dbname $args[1]\u00a0 -filepath $args[2] -relocatefiles $args[3] -force } -ArgumentList $server, $Dbname ,$filepath ,$Realocatefiles\r\n\u00a0\r\nWait-Job $job | Receive-Job\r\nWait-Job $job1 | Receive-Job\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-6.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image012.jpg\" alt=\"1319-clip_image012.jpg\" width=\"567\" height=\"201\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 6 &#8211; Managing 2 Restore jobs with no trouble. (click to enlarge)<\/b><\/p>\n<p>\u00a0How Cool is that?!<\/p>\n<p>\u00a0But now you&#8217;re thinking, &#8220;<i>OK Laerte, that&#8217;s neat, but will I have to hard code all the servers and databases that I want to restore?<\/i>&#8221; And my answer is, &#8220;<i>No&#8230; let&#8217;s use an XML file for that<\/i>&#8220;. First I created an XML file (<b>restore.xml<\/b>) \u00a0with the following structure, and populated it with all the servers and databases I wanted to restore:<\/p>\n<pre class=\"theme:ssms2012 lang:xml\">\u00a0&lt;?xml version=\"1.0\" standalone=\"yes\" ?&gt;\r\n&lt;config&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_8&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_8.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_8.ldf'&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_9&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_9.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_9.ldf'&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_10&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_10.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_10.ldf'&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_11&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_11.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_11.ldf'&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n&lt;\/config&gt;\r\n<\/pre>\n<p>And now, with the addition of the code below, I was able to scale out the final script to run <b>Invoke-SQLRestoringEventing<\/b>:<\/p>\n<pre class=\"theme:ssms2012 lang:xml\">$xmldata = [xml] (Get-Content c:\\temp\\testes\\Restore.xml )\r\n$cmdblock = \"\"\r\n$cmdBlock_1 = \"\"\r\n[int] $counter = 1\r\nforeach( $Value in $xmldata.config.values) \r\n{\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $cmdBlock += \"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 `$server = \"\"$($value.server)\"\"; `n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 `$dbname = \"\"$($value.dbname)\"\"; `n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 `$filepath = \"\"$($value.filepath)\"\"; `n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 `$Realocatefiles = @{$($value.realocatefiles)};`n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 `$job_$($Counter) = Start-Job -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1 -Force} -scriptblock { Invoke-SqlRestoreEventing -sqlserver `$args[0] -dbname `$args[1]\u00a0 -filepath `$args[2] -relocatefiles `$args[3] -force } -ArgumentList `$server, `$dbname ,`$filepath ,`$Realocatefiles ; `n\"\r\n\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $cmdBlock_1 += \"`n wait-job `$job_$($Counter) | receive-job ;\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $counter++\r\n\u00a0\r\n}\r\n\u00a0\r\n$cmdblockTotal = $cmdblock + $cmdBlock_1\r\n$scriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($cmdBlockTotal)\r\ninvoke-command -ScriptBlock $ScriptBlock \r\n<\/pre>\n<p>And just to prove that it works&#8230;<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-7.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image014.jpg\" alt=\"1319-clip_image014.jpg\" width=\"567\" height=\"201\" \/><\/a><\/p>\n<p class=\"caption\"><b>Figure 7 &#8211; PowerShell eventing, working it&#8217;s multi-server management magic. (click to enlarge)<\/b><\/p>\n<p>That&#8217;s all well and good, but then I discovered <a href=\"http:\/\/blogs.msdn.com\/b\/powershell\/archive\/2011\/04\/04\/scaling-and-queuing-powershell-background-jobs.aspx\">a much more elegant way to control the queue<\/a>. The first step was to change the \u00a0realocatefile tag in the XML to a hashtable syntax:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">&lt;?xml version=\"1.0\" standalone=\"yes\" ?&gt;\r\n&lt;config&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_8&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;@{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_8.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_8.ldf'}&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_9&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;@{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_9.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_9.ldf'}&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_10&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;@{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_10.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_10.ldf'}&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n\u00a0 &lt;Values&gt;\r\n\u00a0\u00a0\u00a0 &lt;Server&gt;Vader&lt;\/Server&gt;\r\n\u00a0\u00a0\u00a0 &lt;Dbname&gt;TestPoshEventing_11&lt;\/Dbname&gt;\r\n\u00a0\u00a0\u00a0 &lt;Filepath&gt;c:\\temp\\backup\\TestPoshEventing.bak&lt;\/Filepath&gt;\r\n\u00a0\u00a0\u00a0 &lt;Realocatefiles&gt;@{TestPoshEventing= 'c:\\temp\\restore\\TestPoshEventing_11.mdf';TestPoshEventing_log = 'c:\\temp\\restore\\TestPoshEventing_11.ldf'}&lt;\/Realocatefiles&gt;\r\n\u00a0 &lt;\/Values&gt;\r\n&lt;\/config&gt;\r\n<\/pre>\n<p>.. And then add this code to my function parameters:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">\u00a0\r\n$queue.Enqueue($item)\r\n<\/pre>\n<p>In this simple case, the code only uses one parameter, but I needed tp use more for my function. Thankfully, this was easily solved this by using <b>PSObject (custom objects)<\/b> again :<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">[hashtable] $x = Invoke-Expression ($Value.Realocatefiles)\r\n$pso = new-object psobject -property @{Server=$value.server;DbName=$value.dbname;Filepath=$Value.filepath;Realocatefiles = $x} \r\n$queue.Enqueue($pso)\r\n\u00a0\r\n<\/pre>\n<p>As you can see, I changed the XML to use <b>Invoke-Expression<\/b> and converted the <b>String<\/b> in <b>realocatefiles<\/b> tag to a PowerShell <b>HashTable<\/b>. Next, I added the<b>-messagedata <\/b>parameter into\u00a0 <b>Register-ObjectEvent<\/b> (This tip for how to show the &#8220;Restore Completed&#8221; message is thanks to Ravikanth.)<\/p>\n<p>So finally, using the handy MSDN code with a few changes, the script now looks like this:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$maxConcurrentJobs = 5\r\n$xmldata = [xml] (Get-Content c:\\temp\\testes\\Restore.xml\u00a0 )\r\n$queue = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )\r\nforeach( $Value in $xmldata.config.values) \r\n{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [hashtable] $Realocatefiles = Invoke-Expression ($Value.Realocatefiles)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $pso = new-object psobject -property @{Server=$value.server;DbName=$value.dbname;Filepath=$Value.filepath;Realocatefiles = $Realocatefiles } \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $queue.Enqueue($pso)\r\n}\r\n\u00a0\r\nfunction RunJobFromQueue\r\n\u00a0{\r\n\u00a0\u00a0\u00a0 if( $queue.Count -gt 0)\r\n\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $job = Start-Job\u00a0 -InitializationScript\u00a0 {Import-Module c:\\temp\\testes\\PoshTest.psm1\u00a0 -Force} -scriptblock {Param ($pso);Invoke-SqlRestoreEventing -sqlserver $pso.Server -dbname $pso.dbname\u00a0 -filepath $pso.filepath -relocatefiles $pso.realocatefiles -force } -ArgumentList $queue.Dequeue()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 Register-ObjectEvent -InputObject $job -EventName StateChanged -Action { RunJobFromQueue; receive-job $event.MessageData.Id; Remove-Job $event.MessageData.Id } -MessageData $job | Out-Null\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0}\r\n\u00a0\r\nfor( $i=0;$i -lt $maxConcurrentJobs;$i++ )\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0 RunJobFromQueue\r\n\u00a0}\r\n<\/pre>\n<p>We now have complete control of job queues, and the scaling of the code is more elegant:<\/p>\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-8.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1319-clip_image016.jpg\" alt=\"1319-clip_image016.jpg\" width=\"567\" height=\"365\" \/><\/a><\/p>\n<p><b>Figure 8 &#8211; More elegant PowerShell eventing, still working it&#8217;s multi-server management magic. (click to enlarge)<\/b><\/p>\n<p><a>Even better, you still can work in the same session!<\/a><\/p>\n<h2>Now I&#8217;m Just Showing Off<\/h2>\n<p>So now the solution works, but what if we want a windows task bar balloon notification too, rather than just the console messages? Well, first of all, we have to download the appropriate module from Robertro Belo&#8217;s Blog (Sly PowerShell &#8211; Balloon tip notifications) [link deprecated]\u00a0 and load that module into your PowerShell user profile too. Once you&#8217;ve done that, a little change is required in <b>Invoke-SQLrestoring<\/b>:<\/p>\n<pre class=\"theme:powershell-ise lang:tsql\">$pso = new-object psobject -property @{Server=$server;DbName=$dbname} \r\nRegister-ObjectEvent -InputObject $restore\u00a0 -EventName \"Complete\" -SourceIdentifier CompleteRestore -Action { Import-Module ShowBalloonTip -force;Write-Host \"Server $($event.MessageData.Server), database $($event.MessageData.dbname) restore Completed\";Show-BalloonTip \"Server $($event.MessageData.Server), database $($event.MessageData.dbname) restore Completed\"} -MessageData $pso | Out-Null\r\n... And we need to adjust our script to run with PoshTestBallon PowerShell module (which you can download from the top of this article):\r\n$maxConcurrentJobs = 5\r\n$xmldata = [xml] (Get-Content c:\\temp\\testes\\Restore.xml )\r\n$queue = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )\r\nforeach( $Value in $xmldata.config.values) \r\n{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 [hashtable] $x = Invoke-Expression ($Value.Realocatefiles)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $pso = new-object psobject -property @{Server=$value.server;DbName=$value.dbname;Filepath=$Value.filepath;Realocatefiles = $x} \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 $queue.Enqueue($pso)\r\n}\r\n\u00a0\r\nfunction RunJobFromQueue\r\n\u00a0{\r\n\u00a0\u00a0\u00a0 if( $queue.Count -gt 0)\r\n\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $j = Start-Job\u00a0 -InitializationScript\u00a0 {Import-Module PoshTestBallon.psm1 -Force} -scriptblock {Param ($pso);Invoke-SqlRestoreEventingBallon -sqlserver $pso.Server -dbname $pso.dbname\u00a0 -filepath $pso.filepath -relocatefiles $pso.realocatefiles -force } -ArgumentList $queue.Dequeue()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 Register-ObjectEvent -InputObject $j -EventName StateChanged -Action { RunJobFromQueue; receive-job $sender.Id; Remove-Job $Sender.Id } | Out-Null\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0}\r\n\u00a0\r\nfor( $i=0;$i -lt $maxConcurrentJobs;$i++ )\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0 RunJobFromQueue\r\n\u00a0}\r\n\u00a0\r\n<\/pre>\n<p>Once you&#8217;ve done that, revel in your Power(Shell)&#8230;<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p>Can you see this? Can you feel the power? If you want to see the full solution, please feel free to download it and take a look.<\/p>\n<h2>In Closing<\/h2>\n<p>Well my friends, that&#8217;s the first time I&#8217;ve done anything with PowerShell Eventing, and I&#8217;m now bursting with ideas for what to do next, and I hope you are too. I will finish my article by paraphrasing \u00a0PowerShell MVP and friend Max Trinidad (<a href=\"http:\/\/www.maxtblog.com\/\">blog<\/a>|<a href=\"http:\/\/twitter.com\/#!\/MaxTrinidad\">twitter<\/a>), who is also working on a really cool CodePlex project: <a href=\"http:\/\/pssqldevtools.codeplex.com\/\">SQLDevTools &#8211; PowerShell SQL Server Developers Toos)<\/a><\/p>\n<p><b>Happy PowerShelling!<\/b><\/p>\n<h3>Credits<\/h3>\n<p>Thanks to all the PowerShell\u00a0 Jedis who helped me, Jeffery Hicks (<a href=\"http:\/\/jdhitsolutions.com\/blog\">blog<\/a>|<a href=\"http:\/\/twitter.com\/#!\/JeffHicks\">twitter<\/a>), Marco Shaw (<a href=\"http:\/\/marcoshaw.blogspot.com\/\">Blog<\/a>), Sean\u00a0 Kearney (<a href=\"http:\/\/www.powershell.ca\">blog<\/a>|<a href=\"http:\/\/twitter.com\/#!\/energizedtech\">twitter<\/a>) and especially\u00a0 Ravikanth ( instrumental in finishing this script), who all pointed me in the right directions to create this procedure.<\/p>\n<p>A special thanks goes to my friend and mentor Chad Miller (<a href=\"http:\/\/sev17.com\/\">blog<\/a>|<a href=\"http:\/\/twitter.com\/#!\/cmille19\">twitter<\/a>), who kindly gave permission to use his <b>invoke-sqlrestore<\/b> function in my tests.<\/p>\n<h3>Links for PowerShell<\/h3>\n<ul>\n<li><a href=\"http:\/\/www.ravichaganti.com\/blog\/\">Ravikanth Chaganti Blog<\/a><\/li>\n<li><a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/\">Hey, Scripting Guy!Blog<\/a><\/li>\n<li><a href=\"http:\/\/jdhitsolutions.com\/blog\/\">Jeffery Hicks &#8211; The Lonely Administrator<\/a><\/li>\n<li><a href=\"http:\/\/www.powershell.ca\/\">Sean Kearney Blog<\/a><\/li>\n<li><a href=\"http:\/\/windowsitpro.com\/blog\/powershell-purpose-blog\">Don Jones on PowerShell<\/a><\/li>\n<li><a href=\"http:\/\/www.jonathanmedd.net\/\">Jonathan Medd&#8217;s Blog<\/a><\/li>\n<\/ul>\n<h3>Links for PowerShell with SQL Server<\/h3>\n<ul>\n<li><a href=\"http:\/\/sev17.com\/\">Chad Miller &#8211; Sev17<\/a><\/li>\n<li><a href=\"http:\/\/sqlvariant.com\/\">Aaron Nelson (SQLVariant) Blog<\/a><\/li>\n<li><a href=\"http:\/\/sqlchicken.com\/sql-university\/\">Jorge Segarra &#8211; SQL University Blog <\/a><\/li>\n<li><a href=\"http:\/\/www.maxtblog.com\/\">Max Trinidad &#8211; The PowerShell Front<\/a><\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>When you&#8217;re managing a large number of servers, it makes no sense to run maintenance tasks one at a time, serially. PowerShell is able to  use events, so is ideal for, say, restoring fifty databases on different servers at once, and be notified when each is finished. Laerte shows you how, with a little help from his friends.&hellip;<\/p>\n","protected":false},"author":221715,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[4249,4168,4635,4150,4151],"coauthors":[6819],"class_list":["post-1167","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-backup-and-recovery","tag-database","tag-powershell","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1167","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\/221715"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1167"}],"version-history":[{"count":17,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1167\/revisions"}],"predecessor-version":[{"id":88838,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1167\/revisions\/88838"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1167"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1167"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1167"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}