tag:blogger.com,1999:blog-73677949055518919312018-03-06T11:12:47.963-05:00Tech-CatsTechnical ramblings and discoveriesBen Richardsnoreply@blogger.comBlogger132125tag:blogger.com,1999:blog-7367794905551891931.post-83338183111184944492016-12-14T20:58:00.001-05:002016-12-14T21:23:24.731-05:00Building a Reusable Windows Configuration - A Practical Guide<h2 id="part-i-introduction-and-setup">Part I - Introduction and Setup</h2>
<p>Ever since I learned of the existence of <a href="https://www.symantec.com/products/threat-protection/endpoint-management/ghost-solutions-suite">Ghost</a> (now owned by Symantec), I have used the concept to create a custom Windows configuration. The process was as follows:</p>
<ol>
<li>Partition the physical drive in two partitions.</li>
<li>Install Windows on the first partition. Use the second partition for data.</li>
<li>Install drivers.</li>
<li>Install and configure applications.</li>
<li>Use the system for a few days.</li>
<li>Make an image of the Windows partition and store in a safe place.</li>
<li>Use the machine for 3-6 months, re-image back from the image.</li>
<li>Use the system some more, make adjustments and create an incremental image.</li>
</ol>
<p>This model works, and I still use it. Instead of Ghost, I now use Acronis True Image. True Image is more modern, has compression, provides incremental/differential backups, and more. However, installing and configuring applications (#4 above) has always been a manual and lengthy process. In this guide, I will show you a practical way to create a reusable Windows configuration, one which you can apply on any machine.</p>
<h3 id="goals">Goals</h3>
<ul>
<li>Create a script that can run on any Windows machine.</li>
<li>Ability to install/remove Windows features.</li>
<li>Ability to apply Windows updates.</li>
<li>Ability to install our applications of choice either from a local installer or the web.</li>
<li>Ability to restore settings for the applications of our choosing.</li>
<li>Ability to restore various Windows profile settings.</li>
</ul>
<p>Let’s start!</p>
<p>We will be using the following tools:</p>
<h3 id="nuget"><a href="https://www.nuget.org">NuGet</a></h3>
<p>NuGet is a package manager that is widely known in the Microsoft world. It’s a way to package and redistribute source code, that integrates flawlessly into Visual Studio. NuGet packages have the .nupkg extension, and contain the package manifest (a file called NuSpec using the .nuspec extension, an XML file), and usually the PowerShell script to install the source code/software. The NuGet package file is just a zip file with some metadata attached to it.</p>
<h3 id="chocolatey"><a href="https://chocolatey.org">Chocolatey</a></h3>
<p>Chocolatey takes the NuGet concept to the next level. Chocolatey builds on top of NuGet and allows us to use NuGet packages to install not just source code inside our development environment, but also Windows applications and features. If you don’t have Chocolatey, go to their page and install it by running the PowerShell provided on their page. The <a href="http://chocolatey.org/packages">Chocolatey package gallery</a> has over 4000 packages ready to install. All you do is:</p>
<pre class="prettyprint"><code class=" hljs vala"><span class="hljs-preprocessor"># Install Google Chrome</span>
choco install googlechrome</code></pre>
<h3 id="powershell"><a href="https://technet.microsoft.com/en-us/library/bb978526.aspx">PowerShell</a></h3>
<p>Powershell is a powerful scripting language built on top of the .NET framework. Think BAT or Linux bash but with the whole .NET framework behind it.</p>
<p>By itself Chocolatey will let you install all the applications you want, but we want more than just applications. Also, by default Chocolatey packages will only install from the web (redistribution rights and all). For our purpose, we want to download the installers once, and be able to run everything locally. To provide some of that functionality, we will need to extend Chocolatey with extensions. Chocolatey extensions are just PowerShell modules that Chocolatey loads and we can put our common functionality in those modules. A Chocolatey extension itself is a NuGet/Chocolatey package. Let’s build our extensions first.</p>
<p>Create a new directory, I called mine <strong>Boyan-Choco.Extensions</strong>, inside the directory create a new <strong>.nuspec</strong> file, I called mine <strong>Boyan-Choco.extension.nuspec</strong>. This file will contain the metadata for our extensions. The important metadata are the ID, and the version.</p>
<pre class="prettyprint"><code class=" hljs xml"><span class="hljs-pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">package</span> <span class="hljs-attribute">xmlns</span>=<span class="hljs-value">"http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">metadata</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">id</span>&gt;</span>Boyan-Choco.extension<span class="hljs-tag">&lt;/<span class="hljs-title">id</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">version</span>&gt;</span>1.0.0<span class="hljs-tag">&lt;/<span class="hljs-title">version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">title</span>&gt;</span>Install Helpers<span class="hljs-tag">&lt;/<span class="hljs-title">title</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">authors</span>&gt;</span>Boyan Kostadinov<span class="hljs-tag">&lt;/<span class="hljs-title">authors</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">projectUrl</span>&gt;</span>https://github.com/Boyan-Kostadinov/Packages<span class="hljs-tag">&lt;/<span class="hljs-title">projectUrl</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">owners</span>&gt;</span>Boyan Kostadinov<span class="hljs-tag">&lt;/<span class="hljs-title">owners</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">packageSourceUrl</span>&gt;</span>https://github.com/Boyan-Kostadinov/BoxStarter<span class="hljs-tag">&lt;/<span class="hljs-title">packageSourceUrl</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">summary</span>&gt;</span>Common Install Functionality<span class="hljs-tag">&lt;/<span class="hljs-title">summary</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">description</span>&gt;</span>
Chocolatey installation extensions
<span class="hljs-tag">&lt;/<span class="hljs-title">description</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">metadata</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">package</span>&gt;</span></code></pre>
<p>The actual PowerShell modules will reside in the <strong>extensions</strong> directory (it must be called <strong>extensions</strong>), so create one inside the same directory where the NuSpec file is. Let’s create some essential PowerShell functions. We will split functionality into separate PowerShell modules (PowerShell modules end with the <strong>.psm1</strong> extension). Let’s create the following:</p>
<ul>
<li><strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/ChocoHelpers.psm1">ChocoHelpers.psm1</a></strong> - Will contain functions related to Chocolatey itself.</li>
<li><strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/FileHelpers.psm1">FileHelpers.psm1</a></strong> - Will contain any file related functions.</li>
<li><strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/InstallHelpers.psm1">InstallHelpers.psm1</a></strong> - Will contain any functions that will help us install packages.</li>
<li><strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/..psm1">RegisteryHelpers.psm1</a></strong> - Will contain any functions related to manipulating the registry.</li>
<li><strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/SystemHelpers.psm1">SystemHelpers.psm1</a></strong> - Will contain any system related functions.</li>
<li><strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/WindowsFeatures.psm1">WindowsFeatures.psm1</a></strong> - Will contain functions related to dealing with Windows features.</li>
</ul>
<p>Note: I am not going to cover every function in every module. The full source can be found under my GitHub repository at <a href="https://github.com/Boyan-Kostadinov/BoxStarter/tree/master/Boyan-Choco.extension">https://github.com/Boyan-Kostadinov/BoxStarter/tree/master/Boyan-Choco.extension</a>.</p>
<p>Let’s implement some of the functions in the <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/ChocoHelpers.psm1">ChocoHelpers</a></strong> module. </p>
<p>First is the <strong>Invoke-Commands</strong> function. It will take a file, and a line template, and for each line that doesn’t start with a <strong>#</strong> (which is the PowerShell character for a comment), it will replace the <strong>##token##</strong> with the current file line, and execute that expression by using the PowerShell <strong>Invoke-Expression</strong></p>
<pre class="prettyprint"><code class=" hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Invoke</span>-<span class="hljs-title">Commands</span><span class="hljs-params">([string] <span class="hljs-variable">$file</span>, [string] <span class="hljs-variable">$commandTemplate</span>)</span> {</span>
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$line</span> in Get-Content -Path <span class="hljs-variable">$file</span> | Where-Object {<span class="hljs-variable">$_</span>.trim() -notmatch <span class="hljs-string">'(^\s*$)|(^#)'</span>})
{
<span class="hljs-variable">$commmand</span> = <span class="hljs-variable">$commandTemplate</span>.replace(<span class="hljs-string">"##token##"</span>, <span class="hljs-variable">$line</span>)
Write-Host <span class="hljs-string">"Running: $commmand"</span>
Invoke-Expression <span class="hljs-variable">$commmand</span>
}
}
<span class="hljs-keyword">catch</span> {
Write-Host <span class="hljs-string">"Failed: $($_.Exception.Message)"</span>
}
}</code></pre>
<p>This way we can pass a file with Chocolatey packages, one per line, and have Chocolatey install each one. Our file would look like so:</p>
<pre class="prettyprint"><code class=" hljs bash">GoogleChrome
JRE8
MsSQLServer2014Express --packageParameter <span class="hljs-string">"….."</span> --installArguments <span class="hljs-string">"…."</span></code></pre>
<p>And the template we will pass to the above function will be:</p>
<pre class="prettyprint"><code class=" hljs ruleslanguage">choco install #<span class="hljs-array">#token</span>#<span class="hljs-array"># </span>--execution-timeout <span class="hljs-number">14400</span> -y</code></pre>
<p>Where we call <strong>choco</strong> (shortcut for Chocolatey) with <strong>install</strong> and execution timeout of 4 hours. We also pass the <strong>-y</strong> flag so Chocolatey does not asks us for confirmation for every package.</p>
<p>The function that abstracts that is fairly simple. It takes only the file containing our Chocolatey packages:</p>
<pre class="prettyprint"><code class=" hljs bash">function Install-Applications([string] <span class="hljs-variable">$file</span>)
{
Write-Host <span class="hljs-string">"Installing Applications from <span class="hljs-variable">$file</span>"</span>
<span class="hljs-keyword">if</span> (<span class="hljs-variable">$env</span>:packagesSource) {
<span class="hljs-variable">$packagesSource</span> = <span class="hljs-string">"-s "</span><span class="hljs-string">"<span class="hljs-variable">$env</span>:packagesSource;chocolatey"</span><span class="hljs-string">""</span>
}
Invoke-Commands <span class="hljs-variable">$file</span> <span class="hljs-string">"choco install ##token## --execution-timeout 14400 -y <span class="hljs-variable">$packagesSource</span>"</span>
}</code></pre>
<p>Onto the next helper, the <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/FileHelpers.psm1">FileHelper</a></strong> module.</p>
<p>The most important function here is the <strong>Get-ConfigurationFile</strong> function. This function will be used to get our Chocolatey package configuration file. The file can be a local file, embedded in the package, or a URL of a remote file, that will be downloaded. The function will also take a parameter for a default configuration file, in case one wasn’t provided by the user. Here is the full listing:</p>
<pre class="prettyprint"><code class=" hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Get</span>-<span class="hljs-title">ConfigurationFile</span><span class="hljs-params">()</span>
{</span>
param(
[string] <span class="hljs-variable">$configuration</span>,
[string] <span class="hljs-variable">$defaultConfiguration</span>
)
<span class="hljs-keyword">if</span> ([System.IO.File]::Exists(<span class="hljs-variable">$configuration</span>))
{
<span class="hljs-keyword">return</span> <span class="hljs-variable">$configuration</span>
}
<span class="hljs-keyword">if</span> ((<span class="hljs-variable">$configuration</span> -<span class="hljs-keyword">as</span> [System.URI]).AbsoluteURI -ne <span class="hljs-variable">$null</span>)
{
<span class="hljs-variable">$localConfiguration</span> = Join-Path <span class="hljs-variable">$env</span>:Temp (Split-Path -leaf <span class="hljs-variable">$defaultConfiguration</span>)
<span class="hljs-keyword">if</span> (Test-Path <span class="hljs-variable">$localConfiguration</span>)
{
Remove-Item <span class="hljs-variable">$localConfiguration</span>
}
Get-ChocolateyWebFile <span class="hljs-string">'ConfigurationFile'</span> <span class="hljs-variable">$localConfiguration</span> <span class="hljs-variable">$configuration</span> | Out-<span class="hljs-keyword">Null</span>
<span class="hljs-keyword">return</span> <span class="hljs-variable">$localConfiguration</span>
}
<span class="hljs-keyword">return</span> <span class="hljs-variable">$defaultConfiguration</span>
}</code></pre>
<p>The <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/FileHelpers.psm1">FileHelper</a></strong> also has unzip functions, path helpers, etc., nothing interesting to write home about.</p>
<p>The most interesting, and most useful of the helpers is the <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/InstallHelpers.psm1">InstallHelper</a></strong>. I will cover the <strong>Get-InstallerPath</strong>, <strong>Install-LocalOrRemote</strong> and the <strong>Install-WithScheduledTask</strong> functions.</p>
<p>The purpose of <strong>Get-InstallerPath</strong> is to look for a setup executable, installer executable, installer path, an ISO image or a URL provided in the arguments. It uses the <strong>Get-Parameters</strong> function to parse the Chocolatey provided parameters, and then looks for one of the following in this exact order:</p>
<pre><code>1. Setup path - An executable path provided with the parameter **/setup="Path.To.Exe"**
2. Installer path - An executable that unpacks the setup for the package.
3. A package installer - A combination of the path defined in **$env:packagesInstallers** and the **file** argument.
4. An ISO path - The path to the ISO image provided with the parameter **/iso="Path.To.Exe"**
5. A URL to the installer - Provided in the url argument. The file is downloaded and stored in the file argument.
</code></pre>
<p>The point of all this is to account for all possible scenarios when it comes to installing a package. This will make more sense when we get to the package creation stage.</p>
<p>Once we figured out where the executable of the package is, <strong>Install-LocalOrRemote</strong>, is really simple. It gets the file from the <strong>file</strong> argument and it uses the Chocolatey function <strong>Install-ChocolateyInstallPackage</strong> to install the package. The full listing:</p>
<pre class="prettyprint"><code class=" hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Install</span>-<span class="hljs-title">LocalOrRemote</span><span class="hljs-params">()</span>
{</span>
param(
[Hashtable] $<span class="hljs-built_in">arguments</span>
)
$<span class="hljs-built_in">arguments</span>[<span class="hljs-string">'file'</span>] = Get-InstallerPath $<span class="hljs-built_in">arguments</span>
<span class="hljs-keyword">if</span> ([System.IO.File]::Exists($<span class="hljs-built_in">arguments</span>[<span class="hljs-string">'file'</span>]))
{
Write-Debug <span class="hljs-string">"Installing from: $($arguments['file'])"</span>
Install-ChocolateyInstallPackage @packageArgs
CleanUp
}
<span class="hljs-keyword">else</span> {
<span class="hljs-keyword">throw</span> <span class="hljs-string">'No Installer or Url Provided. Aborting...'</span>
}
}</code></pre>
<p>Things get slightly more interesting with the <strong>Install-WithScheduledTask</strong>. Because some installers (I’m looking at you <strong>Spotify</strong>, shame on you!) don’t allow you to install the application if you are running as administrator, we have to come up with clever hacks to get around that. This function uses the <strong>Windows Task Scheduler</strong> to schedule a task with the installer executable, start that task and then delete the scheduled task. The <strong>StartAsScheduledTask</strong> function is in the <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/SystemHelpers.psm1">SystemHelpers</a></strong> module. Here is the full listing:</p>
<pre class="prettyprint"><code class=" hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">StartAsScheduledTask</span><span class="hljs-params">()</span> {</span>
param(
[string] <span class="hljs-variable">$name</span>,
[string] <span class="hljs-variable">$executable</span>,
[string] <span class="hljs-variable">$arguments</span>
)
<span class="hljs-variable">$action</span> = <span class="hljs-keyword">New</span>-ScheduledTaskAction -Execute <span class="hljs-variable">$executable</span> -Argument <span class="hljs-variable">$arguments</span>
<span class="hljs-variable">$trigger</span> = <span class="hljs-keyword">New</span>-ScheduledTaskTrigger -Once -At (Get-Date)
Register-ScheduledTask -TaskName <span class="hljs-variable">$name</span> -Action <span class="hljs-variable">$action</span> -Trigger <span class="hljs-variable">$trigger</span>
Start-ScheduledTask -TaskName <span class="hljs-variable">$name</span>
Start-Sleep -s <span class="hljs-number">1</span>
Unregister-ScheduledTask -TaskName <span class="hljs-variable">$name</span> -Confirm:<span class="hljs-variable">$false</span>
}</code></pre>
<p>Next comes the <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/RegistryHelpers.psm1">RegistryHelper</a></strong> module. It contains the following registry related functions:</p>
<ul>
<li><strong>Test-RegistryValue</strong> - Checks if the registry value exists at the provided registry path.</li>
<li><strong>Import-RegistrySettings</strong> - Given a file system path, it iterates over all the .reg files found and imports them with the Windows registry utility regedit.</li>
<li><strong>Import-RegistryFile</strong> - Given a file, executable and a process name, it starts the executable, kills the process with the process name provided, and then imports the registry file provided in the file argument. The purpose is to start the program, and have it create it’s default settings, kill it, and then import the provided registry settings.</li>
</ul>
<p>The <strong><a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/SystemHelpers.psm1">SystemHelpers</a></strong> module is nothing special. It defines the <strong>StartAsScheduledTask</strong> function and the <strong>IsSystem32Bit</strong> function.</p>
<p>The <a href="https://github.com/Boyan-Kostadinov/BoxStarter/blob/master/Boyan-Choco.extension/extensions/WindowsFeatures.psm1"><strong>WindowsFeatures</strong></a> module is slightly more interesting. It defines a way to install Windows features by using Chocolatey (and to be exact the special source in Chocolatey called <strong>WindowsFeatures</strong>). <br>
- <strong>Enable-WindowsFeatures</strong> - Provided a file path, passes that file to Invoke-Commands, using the template choco install ##token## -r -source WindowsFeatures -y. <br>
- <strong>Disable-WindwosFeature</strong> - Does the opposite of Enable-WindowsFeatures by using choco uninstall. <br>
- <strong>Enable-WindowsFeature</strong> - Enables a single Windows feature where the argument is the feature name. Uses <strong>Get-WindowsOptionalFeature</strong> and <strong>Enable-WindowsOptionalFeature</strong>, which are only available in Windows 2016 and Windows 10. To get the name of the features you can run choco list -source WindowsFeatures.</p>
<p>Now that we have all the modules defined, we can build our extensions package by navigating to the directory and running</p>
<pre class="prettyprint"><code class=" hljs avrasm">choco pack Boyan-Choco<span class="hljs-preprocessor">.extensions</span><span class="hljs-preprocessor">.nuspec</span></code></pre>
<p>That will produce a <strong>Boyan-Choco.extension.1.0.0.nupkg</strong>, which you can install by hand with</p>
<pre class="prettyprint"><code class=" hljs tex">choco install Boyan-Choco.extension -source P:<span class="hljs-command">\ath</span><span class="hljs-command">\To</span><span class="hljs-command">\NuPkg</span><span class="hljs-command">\Directory</span></code></pre>
<p>I say by hand, because we will not be installing the extension package, but instead it will be a dependencies that gets installed as part of the packages we will create.</p>
<h4 id="continue-to-part-ii-creating-your-packagescoming-soon">Continue to Part II - Creating Your Packages…Coming Soon</h4>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-46939388451585835582016-08-31T18:54:00.001-04:002016-08-31T23:47:58.737-04:00To Var or not to Var<p>&nbsp;</p> <p>I got asked today about my use of “var” in .NET. As it happens in every debate, I forgot all reasons of why I use “var.” After thinking about it, the main reason is, and always will be, “I like var.;” it’s concise, it’s easy to read, and it saves me time typing more complex types. I have never found myself wondering what type the variables is, so I don’t care about the way I declare its type. I only care about how I use that type. But unlike Jim Jeffries common argument for guns: “Fuck you! I like guns!” (as described in his standup on YouTube: <a title="https://www.youtube.com/watch?v=0rR9IaXH1M0" href="https://www.youtube.com/watch?v=0rR9IaXH1M0">https://www.youtube.com/watch?v=0rR9IaXH1M0</a>), which I agree is a perfectly valid argument for doing something, there is more reasons to using “var.” So I will talk about each one in turn.</p> <ol> <li>It’s as readable as the implicit type declaration. It makes no difference whether you declare your type before or after the variable. The following are the same. <br><br><pre class="brush: csharp">List&lt;Widgets&gt; widgets;</pre><br><pre class="brush: csharp">var widgets = List&lt;Widgets&gt;();
</pre><br>
<li>I don’t have to repeat myself. I am lazy, even more so when I write code. I can “get two birds stoned at once!” So doing this: <br><br><pre class="brush: csharp">var widgets = List&lt;Widgets&gt;();</pre><br>Is easier to write than this: <br><br><pre class="brush: csharp">List&lt;Widgets&gt; widgets = new List&lt;Widget&gt;();</pre><br>
<li>I get the benefit of my variable never being null! No more “Object reference not set to instance of an object!” Sure, this is not a problem for simple types like string, int, but it’s a pain in the ass for and lists like List&lt;Widget&gt;. How many times have you declared your list, forgot to initialize it, then try to put junk in it?! I know I have!<br>
<li>Using “var” is only allowed in local scope, which means that every time I declare something with “var”, you know it will be only available in the local scope. Thank you Captain Obvious!<br>
<li>I don’t care what the compiler does with it when it compiles! It’s none of my business.</li></ol>
<h2>A Sidebar on Code Readability</h2>
<p>The bigger issue with readability that most folks miss, is how you name your variables and how you structure your code. Here are a couple of things that make code much harder to read:</p>
<ul>
<li>Variable names after single letters. “L” or “R” might mean something to you if you wrote the code, but to anyone new looking at it, it might as well be Greek. Which is great if you are Greek but not so much for everyone else. How about “Log” or “Result?”
<li>Function size. If your function is bigger than a page, and I have to scroll to read it, you are doing it wrong.
<li>Inconsistent capitalization. A widely accepted standard is that parameters are camelCase, private _variables are small case and can start with “_” and public Properties are title case.
<li>Too many parameters. If a function has more than 2-3 parameters, it’s time to make a object to pass those parameters. Readability goes out the window when function parameters start spanning multiple lines.
<li>Silly patterns that somehow still persists. By that I am talking about variables having the type letter in the name, such as “bFlag” or “iNumber.” Is “b” byte, is it bool? A better choice would be “IsFlag.” There is nothing to be gained by specifying the type in the variable name. The IDE already knows the type, and you have all the intellisense associated with that type.
<li>Multiline strings. Inline SQL is the poster child for this. A slightly better approach is put your multiline string at the bottom of your class and use some string replacement to fill out it’s values at runtime. Use verbatim strings if you want multiline strings that you can copy/paste in other places. Or better, use an ORM, that’s what they are for!</li></ul>
<h2>Credits</h2>
<p>Credit where it’s due, in no particular order: Burton Rheutan, Endurance Idehen, James Allen.</p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-72514092867087373542016-03-23T15:26:00.000-04:002016-03-23T19:19:56.364-04:00Installing Software on Windows the Easy Way (Using Chocolatey)<div class="MsoNormal">I've been using Chocolatey for a while now, and today I want to share the coolness, for those of you that haven't heard of it or used it. It’s much like apt-get for Windows (automatic way to install software from the web).<o:p></o:p></div><div class="MsoNormal"><br />
</div><div class="MsoNormal">You install it by running this on the command line:<o:p></o:p></div><div class="MsoNormal"><br />
</div><div class="MsoNormal"><pre class="brush: bash;">@powershell -NoProfile -ExecutionPolicy Bypass
-Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))"
&amp;&amp; SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
</pre></div><div class="MsoNormal"><br />
</div><div class="MsoNormal">After that, let’s say you want to install Chrome, it’s simple as (running from CMD)<o:p></o:p></div><div class="MsoNormal"><pre class="brush: bash;">choco install chrome
</pre></div><div class="MsoNormal"><br />
</div><div class="MsoNormal">or EverNote:<o:p></o:p></div><div class="MsoNormal"><pre class="brush: bash;">choco install evernote
</pre></div><br />
<div class="MsoNormal">or Node:<o:p></o:p></div><div class="MsoNormal"><pre class="brush: bash;">choco install nodejs.install
</pre></div><br />
<div class="MsoNormal">You just have to remember to run CMD as administrator. Bypass the license prompt by using the "-y" command argument. You can install any of the 3779 packages it supports. They are listed at&nbsp;<a href="https://chocolatey.org/packages">https://chocolatey.org/packages</a></div>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-38142740632992254122016-03-14T19:23:00.001-04:002016-03-14T19:23:06.355-04:00Using NUnit 3.2 with ReSharper Ultimate 10<p><br>I just upgraded my ReSharper to 10 in the hope that it will fully support NUnit 3.2 (the most current version as of the time of me writing this). However that didn’t turn out to be the case. I still had to Google around to find why ReSharper won’t run my tests while using NUnit 3.2. The solution turned out to be simple – install the ReSharper NUnit runner for NUnit 3 through the package console and restart Visual Studio!</p><pre>Install-Package NUnit.ReSharperRunner3
</pre>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-68415461156313078612016-03-13T17:08:00.000-04:002016-03-13T17:30:14.309-04:00Poor Man’s Batch Encoding with HandBrake<p>I wanted to have a way to batch encode a directory (or even multiple directories) full of files without having to use the HandBrake interface to queue each directory. Ideally, I will do this in .NET in the long run but for now, here is a simple way to walk through all the directories and encode all files with the same HandBrake settings.</p><h2>How it Works</h2><ol><li>Deletes any existing files with the names “_encode_info.log” and “_encode_progress.log”<br />
<li>Gets all the files in the directory and sub directories where it runs, ignoring itself<br />
<li>For each file it finds: <ol><li>it gets the name of the directory the file is in, creates a directory with the same name under the “destinationPathRoot.”<br />
<li>Sets the name of the output file to be under the newly created directory under “destinationPathRoot”<br />
<li>If the file already exists, it sets it’s name to have the current date and time (to avoid overwriting)<br />
<li>Calls the EncodeVideo function with the source file path and destination file path<br />
<li>If the destination file path exists after the encoding, it deletes the source file path</li><br />
<br />
</ol></li>
</ol><h2>Setup</h2><ul><li>handBrakeExecutable – Set the path to the HandBrakeCLI executable<br />
<li>destinationPathRoot – The root of the path where to put the encoded files</li><br />
<br />
</ul><h2>How to Use It</h2><p>Copy the contents below and paste it in a .bat file. I name mine according to the HandBrake arguments I’m using. For example “_encode-Q25-1Audio,bat” (meaning using variable quality of 25 with 1 audio stream). Copy the .bat file in the root of the directory where your videos reside. Run it. </p><h2>Code</h2>
<pre class="brush: bash;">@echo off
cls
setlocal enabledelayedexpansion enableextensions
rem Set the path to the Handbrake executable
set handBrakeExecutable=Path\To\HandBrakeCLI.exe
rem Set the root path where files will be encoded to
set destinationPathRoot=Path\To\Done\Videos
if exist "%CD%\_encode_info.log" del "%CD%\_encode_info.log"
if exist "%CD%\_encode_progress.log" del "%CD%\_encode_progress.log"
rem Get all the files in the directory and sub-directories
for /f "tokens=* delims=|" %%G in ('dir /s /a-d /b *.*') do if not %%~xG==.bat (
rem Set the source file path
set sourceFilePath=%%G
rem Get the current directory and remove the trialing slash
set currentDirectory=%%~dpG
set currentDirectoryNoTrailingSlash=!currentDirectory:~0,-1!
rem Get only the name of the current directory
for %%f in (!currentDirectoryNoTrailingSlash!) do set currentDirectoryName=%%~nxf
rem Create the path to the destantion path based on the current directory name
set destinationPath=!destinationPathRoot!\!currentDirectoryName!
rem if the destination doesn't exist, create it
if not exist !destinationPath! (
mkdir !destinationPath!
)
rem Process the file
echo File: %%G
rem Set the destination file based on the destination path and the current file name
set destinationFilePath=!destinationPath!\%%~nG.mp4
rem If the filename already exists, append the current date and time to it
if exist destinationFilePath (
rem Get the current date and time in MMDDYY_HH_MM_SS format
for /f "tokens=1-8 delims=:./ " %%G in ("%date%_%time%") do (
set currentDateTime=%%G%%H%%I_%%J_%%K
)
set destinationFilePath=!destinationPath!\%%~nG-!currentDateTime!.mp4
)
rem Call Handbrake to encode the file
call :EncodeFile "!sourceFilePath!", "!destinationFilePath!"
rem Delete the source file if the destination exists
if exist !destinationFilePath! (
del "!sourceFilePath!"
)
)
:EncodeFile
setlocal
rem echo Source: %~1
rem echo Destination: %~2
rem Call HandBrakeCLI to encode the file
"!handBrakeExecutable!" ^
-I "%~1" ^
-o "%~2" ^
-f mp4 ^
-O ^
--decomb="fast" ^
--crop 0:0:0:0 ^
--strict-anamorphic ^
--modulus 2 ^
-e x264 ^
-q 25.0 ^
--cfr ^
-a 1 ^
--encoder-level="4.0" ^
--encoder-profile=high ^
1&gt; _encode_progress.log ^
2&gt; _encode_info.log
endlocal
</pre>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-9427007233785163112015-04-29T02:07:00.000-04:002015-04-29T02:08:08.017-04:00OctopusDeploy CCTray IntegrationMy CCTray integration for OctopusDeploy is now fully functional!<br />
<br />
Check it out if you want CCTray to report the status of your OctopusDeploy builds.<br />
<br />
You can find the source code on GitHub with an example web forms page that outputs the XML.<br />
<br />
<a href="https://github.com/Boyan-Kostadinov/OctopusDeploy.CCTray" target="_blank">https://github.com/Boyan-Kostadinov/OctopusDeploy.CCTray</a>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-45089559559211232132014-09-28T16:58:00.001-04:002014-09-29T11:32:44.024-04:00Working with the Google Reseller API<p>I spent a few days trying to get this to work so it’s worth a write-up of what it takes to use the API from .NET.</p><h3>Setup</h3><ol><li>Create an Google Apps account in you can use to impersonate the client and get the data. You do that by going to <a href="http://admin.google.com/reseller.domain.com" target="_blank">http://admin.google.com/reseller.domain.com</a> for your Google Apps reseller account.<br />
<li>Enable API Access by going to Security/API Reference and checking “Enable API access.”<br />
<li>Create a new project in <a href="https://console.developers.google.com/project" target="_blank">Google Developer Console</a>. I called mine “ResellerAPI.”<br />
<li>Configure the Reseller API to be available to your project by going to the “ResellerAPI” project/APIs and enabling “Google Apps Reseller API.”<br />
<li>Create a service account credential under the “ResellerAPI” project/Credentials. You will need to download the P12 key, write down the client ID and the client email address.<br />
<li>Back in the Google Apps admin console, enable client API access for the client ID you created in step 5. You do this by going to Advanced Settings/Mange API client access. You will add the client ID and the API scope “https://www.googleapis.com/auth/apps.order.<strong>”</strong></li><br />
<br />
</ol><h3>Project Setup</h3><ol><li>Create a new .NET project. I called mine GoogleApi.<br />
<li>Open the Nuget package manager and install Google.Apis.Reseller.v1 with the command: <i>install-package Google.Apis.Reseller.v1</i><br />
<li>Update the Microsoft.BCL.Async package to the latest version with: <i>install-package Microsoft.Bcl.Async</i> </li><br />
<br />
</ol><p><h3>Variable Setup</h3>We will create all the above settings a local private constants:</p><pre class="brush: csharp">private const string ClientSertificatePath = @"The path to the certificate file from step 5";
private const string ClientSertificatePassword = "notasecret";
private const string ClientAccountEmail = "The client email address from step 5";
private const string ClientImpersonateUser = "Your Google Apps user from step 1";
private const string ApplicationName = "Reseller Sample";
private const int ServiceMaxResults = 100;
</pre><p><h3>Service Setup</h3>We will use a separate function to setup the service:</p><pre class="brush: csharp">/// &lt;summary&gt;
/// Creates the reseller service object
/// with the provided client settings and certificate
/// &lt;/summary&gt;
/// &lt;returns&gt;&lt;/returns&gt;
private ResellerService Setup()
{
// Create a new certificate from the client certificate file
var certificate = new X509Certificate2(ClientSertificatePath, ClientSertificatePassword, X509KeyStorageFlags.Exportable);
// Create new credentials based on the certificate and the client settings
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(ClientAccountEmail)
{
// Set the scope of the request, AppOrderReadonly does not work here
Scopes = new[] { ResellerService.Scope.AppsOrder },
User = ClientImpersonateUser
}.FromCertificate(certificate));
// Return a new service with the client credentials
return new ResellerService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = ApplicationName,
});
}
</pre><p><h3>Get Subscriptions</h3>A separate function will be used recursively to get a list of subscriptions:</p><pre class="brush: csharp">/// &lt;summary&gt;
/// Gets a list of subscriptions
/// &lt;/summary&gt;
/// &lt;param name="service"&gt;The ResellerService object&lt;/param&gt;
/// &lt;param name="listOfSubscriptions"&gt;A list of Subscription to store the returned data&lt;/param&gt;
/// &lt;param name="nextPageToken"&gt;The string containing the next page token&lt;/param&gt;
private void GetSubscriptions(ref ResellerService service, ref List&lt;subscription&gt; listOfSubscriptions, string nextPageToken)
{
listOfSubscriptions = listOfSubscriptions ?? new List&lt;subscription&gt;();
// Create the subscriptions list request
var listResults = service.Subscriptions.List();
// Set the max results and page token
listResults.MaxResults = ServiceMaxResults;
listResults.PageToken = !string.IsNullOrEmpty(nextPageToken) ? nextPageToken : string.Empty;
// Execute the request
var result = listResults.Execute();
if (result.SubscriptionsValue != null)
{
// Add all the subscriptions to the list
listOfSubscriptions.AddRange(result.SubscriptionsValue);
// If the next page token exists
if (result.NextPageToken != null)
{
// Call yourself again passing the next page token
GetSubscriptions(ref service, ref listOfSubscriptions, result.NextPageToken);
}
}
}
</pre><p><h3>Putting it Together</h3>The run function that will setup the service and get a list of subscriptions:</p><pre class="brush: csharp">private void Run()
{
// Setup the service
var service = Setup();
var listOfSubscriptions = new List&lt;subscription&gt;();
Console.WriteLine("Getting a list of subscriptions...");
GetSubscriptions(ref service, ref listOfSubscriptions, string.Empty);
foreach (var s in listOfSubscriptions)
{
Console.WriteLine(s.SubscriptionId + " - " + s.CustomerId);
}
Console.WriteLine("Total: {0}", listOfSubscriptions.Count);
}
</pre><p><h3>Downloads and Source Code</h3>You can download all the working project from my <a href="https://www.dropbox.com/s/fll2ert7tgef4rm/GoogleApi.Reseller-2014-09-29.zip?dl=0" target="_blank">Dropbox</a>. Source code is on <a href="https://github.com/BNMSoft/GoogleApis" target="_blank">GitHub</a>.</p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-88905162075629370972014-09-24T14:18:00.002-04:002014-09-24T14:19:11.554-04:00How to Execute a Sub-Query in SubSonicPretty simple but not easy to find:<br />
<br />
<pre class="brush: vbnet">listOfType = New SubSonic.Select() _
.From(TableName.Schema) _
.Where(TableName.Columns.Id).IsEqualTo( _
New SubSonic.Select(TableName2.Columns.Id) _
.From(TableName2.Schema) _
.Where(TableName2.Columns.Id).IsEqualTo(someId) _
.ExecuteScalar(Of Long)
).ExecuteTypedList(Of TypedList)()
</pre>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-19296903464295717112014-09-21T18:48:00.001-04:002014-09-21T20:04:42.888-04:00My Experience with Roger Beasley Mitsubishi South<p>Website: <a title="http://www.beasleymitsubishisouth.com/" href="http://www.beasleymitsubishisouth.com/">http://www.beasleymitsubishisouth.com/</a><br>Address: 1120 Shelby Lane, Austin TX 78745</p> <p>So I decided I wanted to buy a Mitsubishi Lancer Evolution. I have been eyeing the car for a long time and since next year is going to be the last year they make it, now seems as good as time as any to get one. It’s a little beast of a car! All wheel drive, 4 cylinder, turbo-charged engine that makes driving this thing an awful lot of fun!</p> <p>Looking around, I found that my local Mitsubishi dealer has two in <a href="http://www.beasleymitsubishisouth.com/austin-new-mitsubishi.cfml?selCategory_=&amp;selCategory=&amp;selCategory__=Mitsubishi+%2862%29&amp;selCategory___=LANCER+EVOLUTION&amp;selCategory_=&amp;rtrans=&amp;rext=&amp;rint=&amp;highprice=999999999&amp;rsort=make%2Cmodel%2Cyr&amp;invkey=&amp;rcate=&amp;rmake=Mitsubishi&amp;rmodel=LANCER+EVOLUTION&amp;invquery=1&amp;cart=0&amp;certa=0&amp;fineavail=0&amp;highyear=9999&amp;lowprice=0&amp;lowyear=0&amp;newthisweek=0&amp;picsonly=0&amp;radius=200&amp;rcond=new&amp;refby=dealersite&amp;rsellid=&amp;searchtype=category&amp;selid=2247&amp;showcert=1&amp;showsold=0&amp;soldonly=0&amp;transmission=&amp;type2=&amp;ziphome=0" target="_blank">stock</a> (and here is the <a href="https://www.dropbox.com/s/ee52bc3gzdk7so9/Screenshot%202014-09-21%2016.51.04.png?dl=0" target="_blank">screenshot</a>). And here is where the confusion begins:</p> <ol> <li>They are both listed as Lancer Evolution but the second is GSR and the first one is not. I know for a fact that there are only two models of this car. One is GSR and the other is MR. <li>They are listed at $41,980 and $38,180 but the base MSRP (from the <a href="http://www.mitsubishicars.com/lancer-evolution" target="_blank">Mitsubishi web site</a>)&nbsp; is $34,995, so I assume they must have some extras but I cannot find anything in the listing except the default features and stock photos.</li></ol> <p>So I email the dealer and get a response from Debbie Morgan. She includes a copy of the sticker for each car and I can now clearly see the difference between the two cars. The cheaper ($38,180) car has the navigation package (MSRP $2375) on top of the base $34,995 + $810 destination fee, bringing the total to $38,180. However in her email she also tells me that the special sale price can not be combined with the special APR program that is currently running. I have no idea why that would be when the program on the Mitsubishi web site clearly does not exclude the Evolution.</p> <p>Next step, I schedule a time to come-in and look at the car for Wednesday night (09/17/2014) at 7pm. I get a message from Rick King to confirm my appointment which I do promptly and show at the appointment at 7pm.</p> <p><strong><font size="3">Wednesday</font></strong></p> <p>I get to the dealership, Rick walks out and without as much as a “Hi”, starts treating me like he has known me for years, but in fact we just met. He lets me look inside the car, and lets me start it but tells me I cannot take it for a test drive. At this point, I am starting to wonder how I am expected to spend $40K without test driving the car. He explains that some guy dropped the clutch at a previous test drive, and now the dealer does not let that car be test driven. Sounds unreasonable to me but I go along.</p> <p>He takes me inside the dealership and we start talking about the numbers and what color I want. Rick seems to know very little about the car, and the available options. He leaves me alone to sit there for a while, talking to his manager I presume, without as much as offering some water or coffee. I am not happy with the poor customer service, but I put it with it. After maybe half an hour and some talk with his manager about prices and payments, I am now allowed to take the car on a test drive. I am guessing they didn’t think I was serious about buying it. I take the car on a 15 minute drive and I love it, but I am very careful how I drive it and we don’t go past 80 (even though I want to see what it can do). We get back, we talk about the color of the car, and that I want the black one from another dealer in San Antonio, and I also want several extra packages. Rick has no idea what is included in the packages I am talking about, he says he will look and get back to me. We finish the conversation with him saying he’ll find out about the packages, and me saying I have to sleep on it. I have not committed to buying the car at this point.</p> <p><strong><font size="3">Thursday</font></strong></p> <p>I get a text message from Rick that he is picking my car up today. I am surprised because I have not yet committing to buying the car, and he has not gotten back to me about the packages I wanted. The packages I want are:</p> <ol> <li>Exterior Package ($1500) - FRONT, SIDE, AND REAR AIRDAMS, BRAKE AIR GUIDES, REAR SPOILER EXTENSION <li>Interior Package ($425) - ALUMINUM 5MT SHIFT KNOB, ALUMINUM / LEATHER BRAKE GRIP <li>Led Illumination Package ($335) - FLOOR ILLUMINATION (BLUE LED), INTERIOR LIGHTS <li>Rear Park Assist Sensors ($295)</li></ol> <p>Package total MSRP is $2555.</p> <p><strong><font size="3">Friday</font></strong></p> <p>Everything from here on happens over text messages, as I am at work. Rick does try to call me but I cannot talk on the phone. </p> <p>I ask him about the packages and he gets back to me in a few hours with a price tag of $3999, and tells me that is just for parts, and that installation will be extra. I don’t know what to say. I send him to the Mitsubishi web site where the prices are listed (despite that they are the MSRP). He apologizes and tells me that I am smarter than their parts department. He then sends me another price of $1490 for installation. I tell him to find a way to drop the installation price and we’ll have a deal. I know this is possible since I have gotten it done at another dealer with my last car, and I was paying half of what the Evolution price tag is.</p> <p>Eventually he gets back to me, and drops the installation price to $996 and $500 off the car price. Also tells me he has gotten me a $300 race track pass for the day – nice gesture without a doubt. However, that is still more than I want to spend, especially after the poor customer service and the several lies I caught them into. I tell them exactly what I want again, and that is to drop the installation fee and we’ll have a deal. Rick does not budge and tells me that his manager will be putting the car back on the lot in the morning. No love lost for me, I thank him for his time and consider the deal not happening.</p> <p>What happens next is very unprofessional and makes me want to buy the car from Rick even less. He goes off on me about the 3 days he has spent on this, tries to guilt me into buying the car because he already got it from the other dealer, and also tells me that he has done everything I have asked for. On top of that, he tells me this has never happened to him in the past 8 years. I tell him that I love the car and I would love to have it, but I don’t have to buy it from him, and I don’t have to buy it today. I say that he has done very little to motivate me to buy it from him. I iterate one more time what I want and he keeps going about how it makes no business sense and how nobody will do that. I guess we are done. He sends me a few more angry messages, and I try to be as polite as possible.</p> <p><strong><font size="3">Conclusion</font></strong></p> <p>I would have bought the car, I was 99% there. What it came down to was paying MSRP for the car, extra for the parts and still getting charged for installation. The poor customer service, the lack of knowledge about the car and packages, and the several things that turned out to be a lie, was what killed the deal. Roger Beasley Mitsubishi South, you failed to get my business, but what is worse than that is that I cannot recommend your dealership to anyone.</p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-65173788484383568492009-10-12T18:26:00.000-04:002014-09-21T17:35:30.984-04:00Useful Function for Type Casting Your Query String Values in ASP.NETWhenever you want to get the query string value in ASP.NET, you usually use Request.QueryString. However, that always gives you a string value that is not type cast to the variable you want. So instead of an integer or a Guid, you always get a string. Here is little function that addresses that and gives you the proper type based on the variable type passed in. It’s used as follows:<br />
<pre class="brush: vb" name="code">' Declare a variable of Guid type
Dim assetID As Guid
' Get the value from the query string
getValue("id", assetID)
' Declare a variable of Integer type
Dim imageWidth As Integer
' Get the value from the query string
getValue("imageWidth", imageWidth)</pre><br />
And for the function itself:<br />
<br />
<pre class="brush: vb" name="code">''' <summary>
''' Gets the value of the query string key specified
''' </summary>
''' <typeparam name="T">The data type of the value to be returned</typeparam>
''' <param name="key">The query string key</param>''' <param name="value">The variable to store the value in</param>
Public Shared Sub getValue(Of T)(ByVal key As String, ByRef value As T)
If Not String.IsNullOrEmpty(Request.QueryString.Item(key)) Then
'If the passed in type is Guid
If GetType(T) Is GetType(Guid) Then
' Check the format of the query string for a valid Guid
If isValidGuid(Convert.ToString(Request.QueryString.Item(key))) Then
' Type cast the value from a string to a Guid
value = DirectCast(CType(New Guid(Convert.ToString(Request.QueryString.Item(key))), Object), T)
Else
' Type cast the an empty guid for the value
value = DirectCast(CType(Guid.Empty, Object), T)
End If
Else
' Type cast query string value to the requested type
value = CType(Convert.ChangeType(Request.QueryString.Item(key).Trim(), GetType(T)), T)
End If
End If
End Sub
</pre>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-88570906897174942592008-11-05T23:54:00.001-05:002014-09-21T18:55:24.891-04:00SQL Server Scheduled Backups with NAnt<p>When installing and using production web applications, backup is always a must. I have written about <a href="http://blog.tech-cats.com/2007/10/sql-server-script-to-backup-database-to.html" target="_blank">SQL Server Backup</a> before and this post would expend on that. The main goal here is to create a solution that periodically backs up your database. To do this, we’ll use the backup script from the previous post, employ NAnt to execute the script and zip the created backup. Last, we’ll schedule the execution of the NAnt build script through the Windows Task Scheduler or the “at” command. This procedure can backup a local or a remote SQL Server (as long as the remote server is on the same network).</p><h4>Prerequisites</h4><ul><li>Installed and configured version of NAnt (see <a href="http://blog.tech-cats.com/2007/11/getting-started-with-nant-net-build.html" target="_blank">Getting Started with NAnt - .NET Build Tool</a>) </li>
<li>Installed NAntContrib tasks (for the SQL task, see <a href="http://nantcontrib.sourceforge.net/" target="_blank">NAntContrib</a> on SourceForge) </li>
<li>Task Scheduler Service enabled in Control Panel/Admin Tools/Services </li>
<li>SQL login with “dbo” rights to the database (to install the backupDatabase procedure) </li>
<li>SQL login that has “public” rights to the database (so it can execute the backup) </li>
</ul><h4>Backup SQL Procedure</h4><p>The script below with create the SQL server stored procedure to create a backup file of a given database. It takes two parameters:</p><ol><li>databaseName – The SQL server database to be backed up </li>
<li>backupDirectory – The directory where the backup file will be created </li>
</ol><p>You need to execute it against the database that you will be backing up and give the “public” role execution permissions to the procedure.</p><br />
<strong>Code</strong> <br />
<pre class="brush: sql" name="code">exec dbo.sp_executesql @statement = N'
/*
Created:
07.18.2008 by Boyan Kostadinov (boyank@gmail.com)
Dependencies:
None
Usage:
exec dbo.backupDatabase ''ensembleVideo'', ''C:\Temp''
Parameters:
@databaseName - varchar
- The database to backup
@backupDirectory - varchar
- The path to where the database should be
backed up. This should be an existing directory on
the SQL Server where the database is located
Description:
Backsup a given database to the specified directory
*/
create procedure dbo.backupDatabase
@databaseName varchar(100),
@backupDirectory varchar(1000)
as
declare @backupFileName varchar(100),
@databaseDataFilename varchar(100), @databaseLogFilename varchar(100),
@databaseDataFile varchar(100), @databaseLogFile varchar(100),
@execSql varchar(1000)
-- If the backup directory does not end with ''\'', append one
if charindex(''\'', reverse(@backupDirectory)) &gt; 1
set @backupDirectory = @backupDirectory + ''\''
-- Create the backup file name based on the backup directory, the database name and today''s date
set @backupFileName = @backupDirectory + @databaseName + ''-'' + replace(convert(varchar, getdate(), 110), ''-'', ''.'') + ''.bak''
set @execSql = ''
backup database ['' + @databaseName + '']
to disk = '''''' + @backupFileName + ''''''
with
noformat,
noinit,
name = '''''' + @databaseName + '' backup'''',
norewind,
nounload,
skip''
exec(@execSql)'
go</pre><h4>NAnt Backup Script</h4><p>The script is somewhat complex. Here are the list of features:</p><ul><li>Backup a local SQL Server instance or a SQL Server instance on the same network. The key is that the file system of the networked instance must be available through UNC shares to the machine executing the backup script. </li>
<li>Use a specified connection string or read the connection string from a .NET (or other type of XML)configuration file. </li>
</ul><br />
I will not dive into the script itself since it's properties and flow is documented fairly well. <br />
<br />
<strong>Code</strong> <br />
<br />
<pre class="brush: xml" name="code">&lt;project name=&quot;backupDatabase&quot; default=&quot;run&quot; xmlns=&quot;http://nant.sf.net/release/0.86-beta1/nant.xsd&quot;&gt;
&lt;!-- Set the name of the backup file that will be created --&gt;
&lt;property name=&quot;zipFileName&quot; value=&quot;databaseBackup&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- Set the path where the backup file will be finally stored --&gt;
&lt;property name=&quot;localBackupDirectory&quot; value=&quot;C:\Temp&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- Set the local path (relative to the SQL Server instance) where SQL Server will write the backup file --&gt;
&lt;!-- This has to always be a local path since SQL server can't write to network paths --&gt;
&lt;property name=&quot;sqlServerLocalBackupDirectory&quot; value=&quot;C:\Temp&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- Set the UNC path to the above &quot;sqlServerLocalBackupDirectory&quot; local path --&gt;
&lt;!-- This is only needed if you are backing up SQL server on the network --&gt;
&lt;property name=&quot;sqlServerUNCBackupDirectory&quot; value=&quot;\\beehive\windows$\Temp&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- For backing up a local SQL Server instance, commented the first &quot;sqlServerUNCBackupDirectory&quot; property --&gt;
&lt;!-- and uncommented the one below this line, or set the &quot;sqlServerUNCBackupDirectory&quot; property to &quot;&quot; --&gt;
&lt;!--&lt;property name=&quot;sqlServerUNCBackupDirectory&quot; value=&quot;&quot; overwrite=&quot;false&quot; /&gt;--&gt;
&lt;!-- Set the .NET connection string for connecting to the database --&gt;
&lt;!-- This setting always takes precedence over the config file below --&gt;
&lt;!-- To use a config file instead, set this property to &quot;&quot; (like so value=&quot;&quot;) --&gt;
&lt;property name=&quot;connectionString&quot; value=&quot;Data Source=beehive\sql2005;Initial Catalog=ensembleVideo;User ID=test;Password=test;&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- Alternatively set the path to where the build script should get the connection string from --&gt;
&lt;!-- This is usually a app.config or connectionString.config file --&gt;
&lt;property name=&quot;connectionStringConfigFilePath&quot; value=&quot;C:\Inetpub\wwwroot\myApp\config\connectionStrings.config&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- Set the XPath expression that will be used to grab the connection string from the config file --&gt;
&lt;property name=&quot;connectionStringXPath&quot; value=&quot;/connectionStrings/add[@name = 'sqlServerConnection']/@connectionString&quot; overwrite=&quot;false&quot; /&gt;
&lt;!-- Set the regular expression that's needed to get the database name from the connection string --&gt;
&lt;property name=&quot;getDatabaseNameFromConnecionStringRegEx&quot; value=&quot;Initial Catalog=(?'databaseName'.*?);&quot; overwrite=&quot;false&quot; /&gt;
&lt;property name=&quot;todaysDate&quot; value=&quot;${string::substring(string::replace(datetime::to-string(datetime::now()), '/', '.'), 0, 10)}&quot; /&gt;
&lt;property name=&quot;todaysLocalBackupDirectory&quot; value=&quot;${path::combine(localBackupDirectory, todaysDate)}&quot; /&gt;
&lt;target name=&quot;run&quot;&gt;
&lt;!-- If the SQL Server UNC directory was not specified and the local backup directory exists --&gt;
&lt;if test=&quot;${string::get-length(sqlServerUNCBackupDirectory) == 0 and directory::exists(localBackupDirectory)}&quot;&gt;
&lt;!-- This is a back of a local SQL Server instance --&gt;
&lt;mkdir dir=&quot;${todaysLocalBackupDirectory}&quot; /&gt;
&lt;property name=&quot;todaysSqlServerLocalBackupDirectory&quot; value=&quot;${todaysLocalBackupDirectory}&quot; /&gt;
&lt;property name=&quot;todaysSqlServerUNCBackupDirectory&quot; value=&quot;${todaysLocalBackupDirectory}&quot; /&gt;
&lt;property name=&quot;localSqlServer&quot; value=&quot;true&quot; /&gt;
&lt;/if&gt;
&lt;if test=&quot;${string::get-length(sqlServerUNCBackupDirectory) &gt; 0 and directory::exists(sqlServerUNCBackupDirectory) }&quot; &gt;
&lt;property name=&quot;todaysSqlServerLocalBackupDirectory&quot; value=&quot;${path::combine(sqlServerLocalBackupDirectory, todaysDate)}&quot; /&gt;
&lt;property name=&quot;todaysSqlServerUNCBackupDirectory&quot; value=&quot;${path::combine(sqlServerUNCBackupDirectory, todaysDate)}&quot; /&gt;
&lt;!-- This is a back of a networked SQL Server instance --&gt;
&lt;mkdir dir=&quot;${todaysSqlServerUNCBackupDirectory}&quot; /&gt;
&lt;property name=&quot;localSqlServer&quot; value=&quot;false&quot; /&gt;
&lt;/if&gt;
&lt;!-- If the connection string is empty,
the connection string file exists and the XPath to find the connection string is not empty --&gt;
&lt;if test=&quot;${string::get-length(connectionString) == 0 and file::exists(connectionStringConfigFilePath)
and string::get-length(connectionStringXPath) &gt; 0}&quot; &gt;
&lt;!-- Get the connection string to the database from the connection string config file --&gt;
&lt;xmlpeek
file=&quot;${connectionStringConfigFilePath}&quot;
xpath=&quot;${connectionStringXPath}&quot;
property=&quot;connectionString&quot;&gt;
&lt;/xmlpeek&gt;
&lt;/if&gt;
&lt;!-- If the connectionString property is not empty and the regular expression to get the database name is not empty --&gt;
&lt;if test=&quot;${string::get-length(connectionString) &gt; 0 and string::get-length(getDatabaseNameFromConnecionStringRegEx) &gt; 0}&quot;&gt;
&lt;!-- Get the database name from the connection string --&gt;
&lt;regex pattern=&quot;${getDatabaseNameFromConnecionStringRegEx}&quot; input=&quot;${connectionString}&quot; /&gt;
&lt;!-- Execute the stored procedure to bckup the database --&gt;
&lt;sql connstring=&quot;Provider=SQLOLEDB;${connectionString}&quot; transaction=&quot;false&quot; delimiter=&quot;;&quot; delimstyle=&quot;Normal&quot;&gt;
exec dbo.backupDatabase '${databaseName}', '${todaysSqlServerLocalBackupDirectory}';
&lt;/sql&gt;
&lt;!-- Zip up the created databse backup file --&gt;
&lt;zip zipfile=&quot;${path::combine(todaysSqlServerUNCBackupDirectory, zipFileName + '-' + todaysDate + '.zip')}&quot; ziplevel=&quot;9&quot;&gt;
&lt;fileset basedir=&quot;${todaysSqlServerUNCBackupDirectory}&quot;&gt;
&lt;exclude name=&quot;**/*.zip&quot; /&gt;
&lt;include name=&quot;*.*&quot; /&gt;
&lt;/fileset&gt;
&lt;/zip&gt;
&lt;!-- Delete all other files in the today's backup directory except for the created zip files --&gt;
&lt;delete&gt;
&lt;fileset basedir=&quot;${todaysSqlServerUNCBackupDirectory}&quot;&gt;
&lt;exclude name=&quot;**/*.zip&quot; /&gt;
&lt;include name=&quot;*.*&quot; /&gt;
&lt;/fileset&gt;
&lt;/delete&gt;
&lt;!-- Move the contents of today's backup directory to the local backup directory --&gt;
&lt;move todir=&quot;${localBackupDirectory}&quot;&gt;
&lt;fileset basedir=&quot;${todaysSqlServerUNCBackupDirectory}&quot;&gt;
&lt;include name=&quot;*.*&quot; /&gt;
&lt;/fileset&gt;
&lt;/move&gt;
&lt;!-- Delete the &quot;todays&quot; directories --&gt;
&lt;if test=&quot;${localSqlServer}&quot;&gt;
&lt;delete dir=&quot;${todaysLocalBackupDirectory}&quot; /&gt;
&lt;/if&gt;
&lt;if test=&quot;${not localSqlServer}&quot;&gt;
&lt;delete dir=&quot;${todaysSqlServerUNCBackupDirectory}&quot; /&gt;
&lt;/if&gt;
&lt;/if&gt;
&lt;/target&gt;
&lt;/project&gt;</pre><h4>Setting Up Backup Script and Scheduling</h4>Review the build script and set following properties to match your setup: <br />
<ul><li><strong>zipFileName</strong> – The name of the zip file that will be created for the database backup </li>
<li><strong>localBackupDirectory</strong> – The local directory where the backup will be stored </li>
<li><strong>sqlServerLocalBackupDirectory</strong> - The local path (relative to the SQL Server instance) where SQL Server will write the backup file. This has to always be a local path since SQL server can't write to network paths. </li>
<li><strong>sqlServerUNCBackupDirectory – </strong>The UNC path to the above &quot;sqlServerLocalBackupDirectory&quot; local path. This is only needed if you are backing up SQL server on the network. For backing up a local SQL Server instance, set this &quot;”. </li>
<li><strong>connectionString</strong> - The .NET connection string for connecting to the database. This setting always takes precedence over the “connectionStringConfigFilePath“ setting. To use a configuration file instead, set this property to &quot;&quot;. </li>
<li><strong>connectionStringConfigFilePath</strong> - Alternatively set the path to where the build script should get the connection string from. This is usually a app.config or connectionString.config file. </li>
<li><strong>connectionStringXPath</strong> - The XPath expression that will be used to grab the connection string from the configuration file. </li>
<li><strong>getDatabaseNameFromConnecionStringRegEx</strong> - The regular expression that's needed to get the database name from the connection string. </li>
</ul><p> <br />
The next step is to create the schedule with either the Task Scheduler or with “at” command. <br />
<br />
<strong>To use the Task Scheduler:</strong></p><ol><li>Create a .bat file with the following: “driveLetter:\path\to\nant.exe /f:pathToNAntBackupScript.build” and of course replace that with the actual path to nant.exe and to the NAnt build script you got here. </li>
<li>Go to Control Panel / Scheduled Tasks / Add Scheduled Task </li>
<li>Browse for the .bat file you created in #1 </li>
<li>Configure the schedule run as often as you would like </li>
</ol><p>To use the “at” command:</p><ol><li>Do the same as #1 above. </li>
<li>Open a command prompt and execute the “at” command: <br />
”at 23:00 /every:M,T,W,Th,F pathToBatFileFromStep1.bat” <br />
<br />
That will schedule the task to execute every day of the week at 11:00pm. You can get more info on the “at” command from <a href="http://support.microsoft.com/kb/313565" target="_blank">How To Use the AT Command to Schedule Tasks</a>. </li>
</ol><h4>Bonus</h4><p>You don’t have to hard code the values in the NAnt build script. You can pass them from the command line. In that manner you can reuse the same script for different database. You simply need to call the script with –D:propertyName=”value&quot; like so: <br />
<br />
path\to\nant.exe /f:pathToNAntBackupScript.build –D:zipFileName=&quot;myDatabase&quot; –D:localBackupDirectory=&quot;D:\Temp&quot;</p><h4>Downloads</h4><p><a href="http://tech-cats.net/blog/downloads/sql/procedure-dbo.backupDatabase.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/procedure-dbo.backupDatabase.txt</a> <br />
<a href="http://tech-cats.net/blog/nantScripts/backupDatabase.build.txt" target="_blank">http://tech-cats.net/blog/nantScripts/backupDatabase.build</a> <br />
<a href="http://tech-cats.net/blog/nantScripts/backupDatabase.bat.txt" target="_blank">http://tech-cats.net/blog/nantScripts/backupDatabase.bat</a></p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com2tag:blogger.com,1999:blog-7367794905551891931.post-91006052363349819282008-11-03T21:00:00.000-05:002014-09-30T11:49:57.616-04:00Using NAnt to Traverse Directories and Execute SQL<p>In the latest version of <a href="http://ensembleVideo.com" target="_blank">Ensemble Video</a> I had a need to migrate the old mechanism of showing content to new way that involved a single page and some URL rewriting (more on that another time). The basics of the old mechanism were that for each publishing point used by the application, a separate directory with specific “index.aspx” was created. That was always a major pain when updates had to be applied since I always had to preserve the old files but still update existing content. Long story short, I got away from that but I still needed to figure out what publishing points were in use so I can set the database flag for the new mechanism. So, the NAnt fanatic that I am, I build a NAnt script to do that. In this example, you will learn:</p><ol><li>How to parse XML for a certain value </li>
<li>How to traverse directories with NAnt </li>
<li>How to execute an “if” statement in NAnt </li>
<li>How to get file and directory names using the NAnt built-in functions </li>
<li>How to execute SQL script with SQL task from NAntContrib </li>
</ol><p>The prerequisites are that 1). You have NAnt installed and 2). You have NAntContrib installed.</p><p>So let’s go one by one from the list above and you will see the final script at the end.</p><ol><li>How to parse XML for certain value <pre class="brush: xml" name="code">&lt;xmlpeek
file=&quot;${path::combine(path::get-full-path(webApplicationDirectory), 'config\connectionStrings.config')}&quot;
xpath=&quot;/connectionStrings/add[@name = 'ensembleVideoConnection']/@connectionString&quot;
property=&quot;connectionString&quot; /&gt;</pre><br />
This parses the XML file under webApplicationDirectory\config\connectionStrings.config and uses an XPath expression to get the property of the connectionString with the name ensembleVideoConnection. The value is stored in the NAnt property &quot;connectionString&quot; <br />
</li>
<li>How to traverse directories with NAnt <br />
<pre class="brush: xml" name="code">&lt;foreach item=&quot;Folder&quot; in=&quot;${sitesDirectory}&quot; property=&quot;orgDirectoryName&quot;&gt;
&lt;echo message=&quot;${orgDirectoryName}&quot; /&gt;
&lt;/foreach&gt;</pre><br />
This task traverses through the directories inside the directory specified by &quot;sitesDirectory&quot;, stores the current directory in orgDirectoryName and then echoes the current directory. <br />
</li>
<li>How to execute an “if” statement in NAnt <br />
<pre class="brush: xml" name="code">&lt;if test=&quot;${string::to-lower(path::get-file-name(orgDirectoryName)) != '_svn'}&quot;&gt;
&lt;echo message=&quot;${path::get-file-name(orgDirectoryName)}&quot; /&gt;
&lt;/if&gt;</pre><br />
Here we test if the current directory name is equal to &quot;_svn&quot;. Here is another example where we test if the current directory contains a desired file: <br />
<pre class="brush: xml" name="code">&lt;if test=&quot;${file::exists(path::combine(webSiteDirectoryName, 'index.aspx'))}&quot;&gt;
&lt;property name=&quot;enableQuickPublish&quot; value=&quot;1&quot; /&gt;
&lt;/if&gt;</pre><br />
</li>
<li>How to get file and directory names using the NAnt built-in functions <br />
<pre class="brush: xml" name="code">&lt;-- Get only the directory name --&gt;
path::get-file-name(orgDirectoryName)
&lt;-- Get the directory name from the path --&gt;
path::get-directory-name(orgDirectoryName)
&lt;-- Create a full path based on the directory name and a file name --&gt;
path::combine(webSiteDirectoryName, 'index.aspx')</pre><br />
You can find out more about NAnt built-in functions at <a href="http://nant.sourceforge.net/release/latest/help/functions/" target="_blank">NAnt Functions</a> <br />
</li>
<li>How to execute SQL script with SQL task from NAntContrib <br />
<pre class="brush: xml" name="code">&lt;sql
connstring=&quot;Provider=SQLOLEDB;${connectionString}&quot;
transaction=&quot;true&quot;
delimiter=&quot;;&quot;
delimstyle=&quot;Normal&quot;&gt;
update dbo.webSites
set isQuickPublished = ${enableQuickPublish}
where webSiteID = (
select webSiteID
from dbo.listWebSites
where webSafeOrganizationName = '${path::get-file-name(orgDirectoryName)}'
and webSafeDepartmentName = '${path::get-file-name(departmentDirectoryName)}'
and webSafeWebSiteName = '${path::get-file-name(webSiteDirectoryName)}'
);
&lt;/sql&gt;</pre><br />
There is not much to the SQL statement execution. As you can see you can use NAnt properties and function inside the SQL script. You can check out all of NAntContrib's tasks at <a href="http://nantcontrib.sourceforge.net/release/latest/help/tasks/" target="_blank">NAntContrib Tasks</a> <br />
</li>
</ol><br />
<p>As promised here is the full contents of the build script: <br />
</p><blockquote> <pre class="brush: xml" name="code">&lt;project name=&quot;renameSqlToText&quot; default=&quot;run&quot; xmlns=&quot;http://nant.sf.net/release/0.86-beta1/nant.xsd&quot;&gt;
&lt;!-- Set the path to the web application --&gt;
&lt;property name=&quot;webApplicationDirectory&quot; value=&quot;D:\_Boyan's Documents\_Projects\_ensembleVideo\trunk\webApplication\app&quot; /&gt;
&lt;!-- Set the name of the sites directory (under the applicaiton directory) --&gt;
&lt;property name=&quot;sitesDirectoryName&quot; value=&quot;sites&quot; /&gt;
&lt;property name=&quot;sitesDirectory&quot; value=&quot;${path::combine(path::get-full-path(webApplicationDirectory), sitesDirectoryName)}&quot; overwrite=&quot;true&quot; /&gt;
&lt;target name=&quot;run&quot;&gt;
&lt;!-- Get the database connection string from the web application --&gt;
&lt;xmlpeek
file=&quot;${path::combine(path::get-full-path(webApplicationDirectory), 'config\connectionStrings.config')}&quot;
xpath=&quot;/connectionStrings/add[@name = 'ensembleVideoConnection']/@connectionString&quot;
property=&quot;connectionString&quot; /&gt;
&lt;!-- For each directory in the sitesDirectory --&gt;
&lt;foreach item=&quot;Folder&quot; in=&quot;${sitesDirectory}&quot; property=&quot;orgDirectoryName&quot;&gt;
&lt;if test=&quot;${string::to-lower(path::get-file-name(orgDirectoryName)) != '_svn'}&quot;&gt;
&lt;echo message=&quot;${path::get-file-name(orgDirectoryName)}&quot; /&gt;
&lt;foreach item=&quot;Folder&quot; in=&quot;${orgDirectoryName}&quot; property=&quot;departmentDirectoryName&quot;&gt;
&lt;echo message=&quot; ${path::get-file-name(departmentDirectoryName)}&quot; /&gt;
&lt;foreach item=&quot;Folder&quot; in=&quot;${departmentDirectoryName}&quot; property=&quot;webSiteDirectoryName&quot;&gt;
&lt;echo message=&quot; ${path::get-file-name(webSiteDirectoryName)}&quot; /&gt;
&lt;!-- Set the 'enableQuickPublish' property to false --&gt;
&lt;property name=&quot;enableQuickPublish&quot; value=&quot;0&quot; /&gt;
&lt;!-- If a index.aspx file exists, set the 'enableQuickPublish' property to true --&gt;
&lt;if test=&quot;${file::exists(path::combine(webSiteDirectoryName, 'index.aspx'))}&quot;&gt;
&lt;property name=&quot;enableQuickPublish&quot; value=&quot;1&quot; /&gt;
&lt;/if&gt;
&lt;!-- Run a database query to update the flag in the database --&gt;
&lt;sql
connstring=&quot;Provider=SQLOLEDB;${connectionString}&quot;
transaction=&quot;true&quot;
delimiter=&quot;;&quot;
delimstyle=&quot;Normal&quot;&gt;
update dbo.webSites
set isQuickPublished = ${enableQuickPublish}
where webSiteID = (
select webSiteID
from dbo.listWebSites
where webSafeOrganizationName = '${path::get-file-name(orgDirectoryName)}'
and
webSafeDepartmentName = '${path::get-file-name(departmentDirectoryName)}'
and
webSafeWebSiteName = '${path::get-file-name(webSiteDirectoryName)}'
);
&lt;/sql&gt;
&lt;/foreach&gt;
&lt;/foreach&gt;
&lt;/if&gt;
&lt;/foreach&gt;
&lt;/target&gt;
&lt;/project&gt;</pre></blockquote><h3>Downloads</h3><p><a href="http://tech-cats.net/blog/nantScripts/traverseDirectoriesAndExecuteSQL.build.txt" target="_blank">http://tech-cats.net/blog/nantScripts/traverseDirectoriesAndExecuteSQL.build</a></p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-2567058500762474502008-10-31T01:12:00.001-04:002008-10-31T01:12:31.748-04:00Export UML to XMI from Visio 2003<p>A friend wanted to export his UML diagrams created with Visio to XMI files so then we could run some kind of ColdFusion tool to generate code based on the created diagrams. It appeared however that Visio did not come out of the box a way to do that. Some googling reveled that there is a add-on Microsoft offers but you have to get it, compile it (in Visual C++ non the less) and install it. What a major hassle. So being the problem solver I tend to be, I installed Visual C++ and went at it. About almost an hour later, the procedure below did the trick.</p> <p><strong>Something to Note: </strong>The supported UML diagrams are: Static Structure, Component and Deployment diagrams, and State chart (directly from Microsoft’s documentation about the add-on).</p> <ol> <li>Get the zip file below </li> <li>Copy “XMIEXPRT.DLL” to “C:\Program Files\Microsoft Office\Visio11\DLL\” </li> <li>Copy “VisioUmlAddon.vsl” to “C:\Program Files\Microsoft Office\Visio11\1033\” </li> <li>Open Visio 2003 and: <ol> <li>Go to Tools/Options/Security/Macro Security </li> <li>Set the Macro Security to “Medium” or “Low” </li> <li>Go back to Options and go to Advanced </li> <li>Click on “File Paths” and set the “Add-ons” path to <br />“C:\Program Files\Microsoft Office\Visio11\1033” </li> <li>Restart Visio 2003 </li> </ol> </li> <li>Now you should be able to create a UML diagram and go to Tools/Add-Ons and see “XMI Export” all the way on the bottom. </li> </ol> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p><strong>Downloads</strong></p> <p><a href="http://tech-cats.net/blog/downloads/UML-to-XMI-for-Visio2003.zip" target="_blank">http://tech-cats.net/blog/downloads/UML-to-XMI-for-Visio2003.zip</a></p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com3tag:blogger.com,1999:blog-7367794905551891931.post-26386577634180090182008-07-23T12:18:00.000-04:002014-09-21T18:58:16.278-04:00Rename All Files in a Directory with NAnt<p>It’s a pretty simple thing to do but I had to spend a few minutes today to figure out how to accomplish it. So here is a quick build file to rename all .sql files in a specified directory (relative to the script run directory) to .txt. Funny enough, this scripts gets called by my automation program before it uploads any SQL scripts to my blog. There is nothing to it and there are comments so here goes:</p><pre class="brush: xml" name="code">&lt;project name=&quot;renameSqlToText&quot; default=&quot;run&quot; xmlns=&quot;http://nant.sf.net/release/0.86-beta1/nant.xsd&quot;&gt;
&lt;!-- The relative path to the directory where the files are located --&gt;
&lt;property name=&quot;sqlDirectory&quot; value=&quot;..\downloads\sql&quot; /&gt;
&lt;target name=&quot;run&quot;&gt;
&lt;!-- For each file in the directory --&gt;
&lt;foreach item=&quot;File&quot; in=&quot;${sqlDirectory}&quot; property=&quot;fileName&quot;&gt;
&lt;!-- If the file has the .sql extension --&gt;
&lt;if test=&quot;${string::to-lower(path::get-extension(fileName)) == '.sql'}&quot;&gt;
&lt;!-- Rename the file to .txt extension --&gt;
&lt;move file=&quot;${fileName}&quot; tofile=&quot;${path::combine(path::get-directory-name(fileName), path::get-file-name-without-extension(fileName) + '.txt')}&quot; /&gt;
&lt;/if&gt;
&lt;/foreach&gt;
&lt;/target&gt;
&lt;/project&gt;</pre><h3>Downloads</h3><p><a href="http://tech-cats.net/blog/nantScripts/renameSqlToText.build.txt" target="_blank">http://tech-cats.net/blog/nantScripts/renameSqlToText.build</a></p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com2tag:blogger.com,1999:blog-7367794905551891931.post-67341461364202269022008-07-22T15:04:00.000-04:002014-09-21T18:59:33.279-04:00Silly Little SQL Server Information Schema Helpers<p>If you work with SQL Server like I do, it is often necessary to drop functions and/or procedures through T-SQL. There are two ways to do this:</p><ol><li>Using the <strong>sysobjects</strong> built-in to SQL Server </li>
<li>Using the information schema </li>
</ol><p>Microsoft says they do not recommend the <strong>sysobjects</strong> way since things might change in future version of SQL Server. Regardless, just for reference, here is how to drop a view if it exists:</p><pre class="brush: sql" name="code">if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[listVideos]') and objectproperty(id, N'IsView') = 1)
drop view [dbo].[listVideos]
go</pre><p>Doing the same with a stored procedure is not really much different:</p><pre class="brush: sql" name="code">if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[addEncoding]') and objectproperty(id, N'IsProcedure') = 1)
drop procedure dbo.addEncoding
go</pre><p>Now for the more recommended way, using the information schema:</p><p>Dropping a view:</p><pre class="brush: sql" name="code">if exists (select * from information_schema.views where table_name = 'listVideos')
drop view dbo.listVideos
go</pre><p></p><p>Dropping a stored procedure:</p><pre class="brush: sql" name="code">if exists (select * from information_schema.routines where routine_name = 'addEncoding' and routine_type = 'procedure')
drop procedure dbo.addEncoding
go</pre><p>That is all good and swell, but if you are like me, you might hate to write that long SQL statement every time, so I thought I would shorten it a bit. Here is what I came up with for querying the information schema for procedures and functions. The same can be easily applied to views and tables.</p><p>First, we need a single function to check if a procedure or a function exists. I called it “<strong>routineExists</strong>”. You can download it as a text file from <a href="http://tech-cats.net/blog/downloads/sql/routineExistsFunction.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/routineExistsFunction.txt</a>. Here is the script to create it in your database:</p><pre class="brush: sql" name="code">if exists (select * from information_schema.routines where routine_name = 'routineExists' and routine_type = 'function')
drop function dbo.routineExists
go
exec dbo.sp_executesql @statement = N'
/*
Created:
07.22.2008 by Boyan Kostadinov (boyank@gmail.com)
Dependencies:
information_schema.routines
Parameters:
@routineName varchar(255)
- The name of the procedure or function
@routineType varchar(25)
- The type of routine (usually ''procudure'' or ''function''
Usage:
select dbo.routineExists(''getCopyInformation'', ''procedure'')
select dbo.routineExists(''getCopyInformation'', ''function'')
if (select dbo.routineExists(''getCopyInformation'', ''procedure'')) = 1 print ''exists''
Returns:
0 if the routine does not exist
1 if the routine exists
Description:
Checks the information schema view for the existance of the routine name passed in
*/
create function dbo.routineExists(@routineName varchar(255), @routineType varchar(25))
returns bit
as
begin
declare @routineExists bit
set @routineExists = 0
if exists (
select routine_name
from information_schema.routines
where routine_name = @routineName
and
routine_type = @routineType
) set @routineExists = 1
else set @routineExists = 0
return @routineExists
end
'</pre><p>The usage is quite simple:</p><pre class="brush: sql" name="code">-- Does the stored procedure 'getCopyInformation' exists, will return 0 or 1
select dbo.routineExists('getCopyInformation', 'procedure')
-- Does the function 'getCopyInformation' exists, will return 0 or 1
select dbo.routineExists('getCopyInformation', 'function')
-- If the procedure 'getCopyInformation' exists, print 'exists'
if (select dbo.routineExists('getCopyInformation', 'procedure')) = 1 print 'exists'</pre><p></p><p>Better but still not quite satisfactory. Next step is wrap the &quot;<strong>routineExists</strong>” function in individual functions, one for procedures and one for functions.</p><p>Let’s start with the procedures one. You can download it from <a href="http://tech-cats.net/blog/downloads/sql/procedureExistsFunction.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/procedureExistsFunction.txt</a>. I creatively called it “<strong>procedureExists</strong>”:</p><pre class="brush: sql" name="code">if (select dbo.routineExists('procedureExists', 'function')) = 1 drop function dbo.procedureExists
go
exec dbo.sp_executesql @statement = N'
/*
Created:
07.22.2008 by Boyan Kostadinov (boyank@gmail.com)
Dependencies:
information_schema.routines
Parameters:
@procedureName varchar(255)
- The name of the procedure
Usage:
select dbo.procedureExists(''getCopyInformation'')
if (select dbo.procedureExists(''getCopyInformation'')) = 1 print ''exists''
Returns:
0 if the procedure does not exist
1 if the procedure exists
Description:
A wrapper for the dbo.routineExists that explicetly checks
for the existance of the procedure name passed in
*/
create function dbo.procedureExists(@procedureName varchar(255))
returns bit
as
begin
return (select dbo.routineExists(@procedureName, ''procedure''))
end
'</pre><p>Usage is pretty much the same as the “<strong>routineExists</strong>” function without the “routineType” parameter:</p><pre class="brush: sql" name="code">-- Does the stored procedure 'getCopyInformation' exists, will return 0 or 1
select dbo.procedureExists('getCopyInformation')
-- If the procedure 'getCopyInformation' exists, print 'exists'
if (select dbo.procedureExists('getCopyInformation')) = 1 print 'exists'</pre><p>The function one is called “<strong>functionExists</strong>”. You can download it from <a href="http://tech-cats.net/blog/downloads/sql/functionExistsFunction.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/functionExistsFunction.txt</a>.</p><pre class="brush: sql" name="code">if (select dbo.routineExists('functionExists', 'function')) = 1 drop function dbo.functionExists
go
exec dbo.sp_executesql @statement = N'
/*
Created:
07.22.2008 by Boyan Kostadinov (boyank@gmail.com)
Dependencies:
information_schema.routines
Parameters:
@functionName varchar(255)
- The name of the function
Usage:
select dbo.functionExists(''getCopyInformation'')
if (select dbo.functionExists(''getCopyInformation'')) = 1 print ''exists''
Returns:
0 if the function does not exist
1 if the function exists
Description:
A wrapper for the dbo.routineExists that explicetly checks
for the existance of the function name passed in
*/
create function dbo.functionExists(@functionName varchar(255))
returns bit
as
begin
return (select dbo.routineExists(@functionName, ''function''))
end
'</pre><p></p><p></p><p></p><p></p><p></p><p>Example Usage:</p><pre class="brush: sql" name="code">-- Does the function 'listToTable' exists, will return 0 or 1
select dbo.functionExists('listToTable')
-- If the function 'getCopyInformation' exists, print 'exists'
if (select dbo.functionExists('listToTable')) = 1 print 'exists'</pre><p></p><p></p><p></p><h3>Downloads</h3><p><a href="http://tech-cats.net/blog/downloads/sql/routineExistsFunction.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/routineExistsFunction.txt</a> <br />
<a href="http://tech-cats.net/blog/downloads/sql/procedureExistsFunction.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/procedureExistsFunction.txt</a> <br />
<a href="http://tech-cats.net/blog/downloads/sql/functionExistsFunction.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/functionExistsFunction.txt</a></p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-41876642085321715522008-07-11T15:30:00.007-04:002014-09-21T18:59:55.241-04:00Using "select in" with SubSonicWhile looking for SubSonic related information on something totally unrelated, I came across a post about trying to use the T-SQL "in" keyword to build a SubSonic collection at <a href ="http://www.vitaminzproductions.com/technology-blog/index.php/2008/06/25/subsonic-select-where-in-solution">http://www.vitaminzproductions.com/technology-blog/index.php/2008/06/25/subsonic-select-where-in-solution</a>. If that sounds confusing, imagine that you would like to have SubSonic return data that you would usually get with the following T-SQL statement: <br />
<pre class="brush: sql" name="code">select *
from MyTable
where id in ('val1', 'val2', 'val3')
</pre><br />
The author of the blog post had a solution but it was not one I liked. It seemed like a hack and I hate hacks. So, I took a few minutes and here is the result. The key is using the new SubSonic.Select query available in SubSonic v2.1. The code offers 3 different ways to accomplish the task. <br />
C# Code: <pre class="brush: csharp" name="code">// Define your collection object
MyTableCollection MyTableQuery;
// Define an array list for the first example
ArrayList ids = new ArrayList();
// Add some Guid values to the array list
ids.Add(new Guid("80FB189C-C3EA-4774-B15E-018DA88D3FE2"));
ids.Add(new Guid("88DE9C1D-26A7-4545-AA1E-01BDAEFF0588"));
// Poplulate the collection using an array list
MyTableQuery = new SubSonic.Select()
.From(MyTable.Schema)
.Where(MyTable.Columns.Id)
.In(ids)
.ExecuteAsCollection<mytablecollection>();
// Poplulate the collection using an array of strings
MyTableQuery = new SubSonic.Select()
.From(MyTable.Schema)
.Where(MyTable.Columns.Id)
.In(new string[] {
"80FB189C-C3EA-4774-B15E-018DA88D3FE2",
"88DE9C1D-26A7-4545-AA1E-01BDAEFF0588"})
.ExecuteAsCollection<mytablecollection>();
// Poplulate the collection using an inline select statement
MyTableQuery = new SubSonic.Select()
.From(MyTable.Schema)
.Where(MyTable.Columns.Id)
.In(new SubSonic.Select(MyTable.Columns.Id)
.Top("3")
.From(MyTable.Schema))
.ExecuteAsCollection<mytablecollection>();
</pre>VB.NET Code: <pre class="brush: vb" name="code">' Define your collection object
Dim MyTableQuery As MyTableCollection
' Define an array list for the first example
Dim ids As New ArrayList
' Define an array list for the first example
ids.Add(New Guid("80FB189C-C3EA-4774-B15E-018DA88D3FE2"))
ids.Add(New Guid("88DE9C1D-26A7-4545-AA1E-01BDAEFF0588"))
' Poplulate the collection using an array list
MyTableQuery = New SubSonic.Select() _
.From(MyTable.Schema) _
.Where(MyTable.Columns.Id) _
.In(ids).ExecuteAsCollection(Of MyTableCollection)()
' Poplulate the collection using an array of strings
MyTableQuery = New SubSonic.Select() _
.From(MyTable.Schema) _
.Where(MyTable.Columns.Id) _
.In(New String() {"80FB189C-C3EA-4774-B15E-018DA88D3FE2", "88DE9C1D-26A7-4545-AA1E-01BDAEFF0588"}) _
.ExecuteAsCollection(Of MyTableCollection)()
' Poplulate the collection using an inline select statement
MyTableQuery = New SubSonic.Select() _
.From(MyTable.Schema) _
.Where(MyTable.Columns.Id) _
.In(New SubSonic.Select(MyTable.Columns.Id) _
.Top("3") _
.From(MyTable.Schema)) _
.ExecuteAsCollection(Of MyTableCollection)()
</pre>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com2tag:blogger.com,1999:blog-7367794905551891931.post-21839842879868418902008-04-10T15:59:00.001-04:002008-04-10T16:00:21.138-04:00Code Camp 9 Presentations<p>This past weekend, I attended Code Camp 9 in Waltham, MA and gave two presentation:</p> <ul> <li>No Pain Database Access with Subsonic </li> <li>Windows and Development Automation with NAnt </li> </ul> <p>I am finally getting around to posting the presentation materials and supporting files here. So here goes.</p> <h3>No Pain Database Access with Subsonic</h3> <p>While I was disappointed with the turnout for this presentation and with my preparation, I learned a couple of things:</p> <ol> <li>I know very little about SubSonic. It's far from only an ORM framework, it's much more, and I don't know didly about it. </li> <li>I got to stop procrastinating </li> </ol> <p>The presentation is available in two forms:</p> <ol> <li>Online at Google Docs: <br /><a title="http://docs.google.com/Present?docid=dcz9szc5_25cz3xbtcx&amp;skipauth=true" href="http://docs.google.com/Present?docid=dcz9szc5_25cz3xbtcx&amp;skipauth=true" target="_blank">http://docs.google.com/Present?docid=dcz9szc5_25cz3xbtcx&amp;skipauth=true</a> </li> <li>PDF format: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/NoPainDatabaseAccessWithSubSonic.pdf" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/NoPainDatabaseAccessWithSubSonic.pdf" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/NoPainDatabaseAccessWithSubSonic.pdf</a> </li> </ol> <p>Support files are also available in two forms:</p> <ol> <li>Online at Google Docs: <br /><a title="http://docs.google.com/View?docid=dcz9szc5_28f8wb97fc" href="http://docs.google.com/View?docid=dcz9szc5_28f8wb97fc" target="_blank">http://docs.google.com/View?docid=dcz9szc5_28f8wb97fc</a> </li> <li>HTML version: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/No_Pain_Database_Access.html" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/No_Pain_Database_Access.html" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/No_Pain_Database_Access.html</a> </li> </ol> <p><strong>Downloads</strong></p> <ul> <li>Database create script <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/createDB.txt" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/createDB.txt" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/createDB.txt</a> </li> <li>Example web site <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/subSonicWeb.zip" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/subSonicWeb.zip" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/subSonicWeb.zip</a> </li> </ul> <p>&#160;</p> <h3>Windows and Development Automation with NAnt</h3> <p>I love NAnt. This presentation turned out great. I was prepared, I had lots of examples and people were interested - cool!</p> <p>Again, the presentation is available in two forms:</p> <ol> <li>Online at Google Docs: <br /> <a title="http://docs.google.com/Present?docid=dcz9szc5_24fzjs9zgc&amp;skipauth=true" href="http://docs.google.com/Present?docid=dcz9szc5_24fzjs9zgc&amp;skipauth=true" target="_blank">http://docs.google.com/Present?docid=dcz9szc5_24fzjs9zgc&amp;skipauth=true</a> </li> <li>PDF format: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/WindowsAndDevelopmentAutomationWithNAnt.pdf" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/WindowsAndDevelopmentAutomationWithNAnt.pdf" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/WindowsAndDevelopmentAutomationWithNAnt.pdf</a> </li> </ol> <p>Support Files:</p> <ol> <li>Online at Google Docs: <br /><a title="http://docs.google.com/Doc?id=dcz9szc5_26gnfrmkfv" href="http://docs.google.com/Doc?id=dcz9szc5_26gnfrmkfv" target="_blank">http://docs.google.com/Doc?id=dcz9szc5_26gnfrmkfv</a> </li> <li>HTML version: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/Windows_and_Development.html" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/Windows_and_Development.html" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/Windows_and_Development.html</a> </li> </ol> <p><strong>Downloads</strong></p> <ul> <li>NAnt Examples: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAntExamples.zip" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAntExamples.zip" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAntExamples.zip</a> </li> <li>Sample .NET Project: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/databaseInstaller.zip" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/databaseInstaller.zip" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/databaseInstaller.zip</a> </li> <li>NAnt binaries (v0.85): <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAnt.zip" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAnt.zip" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAnt.zip</a> </li> <li>NAnt docs: <br /><a title="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAntDocs.zip" href="http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAntDocs.zip" target="_blank">http://tech-cats.net/blog/downloads/dotnet/codeCamp9/nantAutomation/NAntDocs.zip</a></li> </ul> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-47318778521533183502008-03-27T15:57:00.002-04:002014-09-21T19:09:59.115-04:00Hey WMI, Where Can I Print<p>Windows Management Instrumentation, or WMI for short, is a very powerful and often under used technology. I am simply fascinated by how much you can accomplish using WMI. It is mostly intended for computer management tasks but it can be used for much more. Some examples include getting all kinds of computer information, managing system services and application. This article will cover a possible solution for listing all available printers on a particular computer and show you to get this information through ColdFusion. WMI can be used through VBScript, C++ and .NET. However since part of my current work is done in .NET, the solution relies on .NET and IIS. Further more, here are the requirements that the we will satisfy:</p><ul><li>List all available printers including shared printers </li>
<li>Allow for filtering the list of printers by the printer name </li>
<li>Be language independent </li>
</ul><p>Accomplishing the first two tasks is fairly easy with WMI. So lets get started.</p><h2>List All Available Printers</h2><p>This is pretty straight forward using C# provided that you reference the System.Management namespace and add the appropriate &quot;using&quot; directive:</p><pre class="brush: csharp" name="code">using System.Management;</pre><p> <br />
Next, we need to declare some variable to query WMI:</p><pre class="brush: csharp" name="code">ObjectQuery oq;
ConnectionOptions co;
ManagementScope ms;
ManagementObjectSearcher mos;
ManagementObjectCollection moc;</pre><p> <br />
Set the connection options:</p><pre class="brush: csharp" name="code">co = new System.Management.ConnectionOptions();
co.Authentication = System.Management.AuthenticationLevel.Default;
co.Impersonation = System.Management.ImpersonationLevel.Impersonate;</pre><p> <br />
Connect to the local machine:</p><pre class="brush: csharp" name="code">// Set the management scope to the local computer
ms = new ManagementScope(&quot;\\\\&quot; + Environment.MachineName, co);</pre><p> <br />
And get the list of printers:</p><pre class="brush: csharp" name="code">// Create an object query to get the printer list
oq = new ObjectQuery(&quot;select * from Win32_Printer&quot;);
mos = new ManagementObjectSearcher(ms, oq);
moc = mos.Get();</pre><p> <br />
What is left to do is just loop through the management objects found and display/aggregate the printer properties:</p><pre class="brush: csharp" name="code">if (moc != null)
{
// Loop through the found printers and collect the information
foreach (ManagementObject mo in moc)
{
// Do something with the printer name
// mo.Properties[&quot;Name&quot;].Value.ToString();
}
}</pre><br />
<h2>Filtering By Printer Name</h2><p>There is two ways that this can be accomplished. Let's explore both.</p><ol><li>Using WMI query clause. <br />
WMI on Windows XP or newer supports the &quot;like&quot; operator so filtering the printer list by the printer name or a partial printer name is very trivial: <br />
<br />
<pre class="brush: csharp" name="code">// Create an object query to get the printer list
oq = new ObjectQuery(&quot;select * from Win32_Printer where Name like \&quot;%myPrinterName%\&quot;&quot;);</pre><br />
Where &quot;myPrinterName&quot; is the full name or part of the name of the printer. Notice to use of &quot;%&quot; as those work as a wildcard.&#160; <br />
</li>
<li>Using simple string compare. <br />
Since there are plenty of servers still using Windows 2000 the string compare method is more compatible: <br />
<br />
<pre class="brush: csharp" name="code">if (moc != null)
{
// Loop through the found printers and collect the information
foreach (ManagementObject mo in moc)
{
if (mo.Properties[&quot;Name&quot;].Value.ToString().ToLower().Contains(&quot;myPrinterName&quot;))
{
// Do something with the printer name
// mo.Properties[&quot;Name&quot;].Value.ToString();
}
}
}</pre></li>
</ol><h2>Language Independence</h2><p>A simple way to implement languages independence is through a web service. So we take the code above and wrap it in a web service. The two addition are the separation of the code in two functions (<strong><em>getPrinters()</em></strong> and <strong><em>getPrintersWithNameFilter()</em></strong>) and the addition of a small helper function called <strong><em>saveProperties() . </em></strong>Here is the final result:</p><pre class="brush: csharp" name="code">using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Management;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;
namespace systemInfo
{
/// &lt;summary&gt;
/// Contains system related functions
/// &lt;/summary&gt;
[WebService(Namespace = &quot;http://webapps/systemInfo&quot;)]
[Description(&quot;Contains a set of printer related functions&quot;)]
public class systemInfo : System.Web.Services.WebService
{
/// &lt;summary&gt;
/// Returns a list of available printers
/// &lt;/summary&gt;
/// &lt;returns&gt;systemData object serialized to XML&lt;/returns&gt;
[WebMethod]
[Description(&quot;Returns a list of available printers&quot;)]
public systemData getPrinters()
{
return getPrintersWithNameFilter(null);
}
/// &lt;summary&gt;
/// Returns a list of available printers filtered by the name specified
/// &lt;/summary&gt;
/// &lt;param name=&quot;nameFilter&quot;&gt;The name of the printer to filter by&lt;/param&gt;
/// &lt;returns&gt;systemData object seralized to XML&lt;/returns&gt;
[WebMethod(MessageName = &quot;getPrintersWithNameFilter&quot;)]
[Description(&quot;Returns a list of available printers filtered by the name specified&quot;)]
public systemData getPrintersWithNameFilter(string nameFilter)
{
ObjectQuery oq;
ConnectionOptions co;
ManagementScope ms;
ManagementObjectSearcher mos;
ManagementObjectCollection moc;
int counter = 0;
ArrayList properties = null;
systemDataPrinter printer = null;
systemData systemDataInstance = new systemData();
if (!String.IsNullOrEmpty(nameFilter))
{
nameFilter = nameFilter.ToLower();
}
co = new System.Management.ConnectionOptions();
co.Authentication = System.Management.AuthenticationLevel.Default;
co.Impersonation = System.Management.ImpersonationLevel.Impersonate;
// Set the management scope to the local computer
ms = new ManagementScope(&quot;\\\\&quot; + Environment.MachineName, co);
// Create an object query to get the printer list
oq = new ObjectQuery(&quot;select * from Win32_Printer&quot;);
mos = new ManagementObjectSearcher(ms, oq);
moc = mos.Get();
if (moc != null)
{
// Create an array of printer objects to store the printer data
systemDataInstance.printer = new systemDataPrinter[moc.Count];
// Loop through the found printers and collect the information
foreach (ManagementObject mo in moc)
{
properties = new ArrayList();
properties.Add(mo.Properties[&quot;Name&quot;]);
properties.Add(mo.Properties[&quot;Location&quot;]);
properties.Add(mo.Properties[&quot;ShareName&quot;]);
properties.Add(mo.Properties[&quot;Description&quot;]);
properties.Add(mo.Properties[&quot;Caption&quot;]);
if (!String.IsNullOrEmpty(nameFilter))
{
// Filter the printers returned by the name filter specified
// Also, exclude printers that start with &quot;__&quot; since those are printer sessions
if (mo.Properties[&quot;Name&quot;].Value.ToString().ToLower().Contains(nameFilter)
&amp;&amp; !mo.Properties[&quot;Name&quot;].Value.ToString().StartsWith(&quot;__&quot;))
{
printer = new systemDataPrinter();
// Save all the properties from the array list
// to the printer object
saveProperties(properties, ref printer);
// Add the current printer object to the array of printers
systemDataInstance.printer[counter] = printer;
counter++;
}
}
// Exclude printers that start with &quot;__&quot; since those are printer sessions
else if (!mo.Properties[&quot;Name&quot;].Value.ToString().StartsWith(&quot;__&quot;))
{
printer = new systemDataPrinter();
// Save all the properties from the array list
// to the printer object
saveProperties(properties, ref printer);
// Add the current printer object to the array of printers
systemDataInstance.printer[counter] = printer;
counter++;
}
}
}
return systemDataInstance;
}
/// &lt;summary&gt;
/// Saves the properties passed by the ManagementObject into
/// an instance of the printer object
/// &lt;/summary&gt;
/// &lt;param name=&quot;properties&quot;&gt;A list of printer properties&lt;/param&gt;
/// &lt;param name=&quot;printer&quot;&gt;The printer object where the properties will be stored&lt;/param&gt;
private void saveProperties(ArrayList properties, ref systemDataPrinter printer)
{
PropertyInfo pi = null;
string value = String.Empty;
foreach (PropertyData prop in properties)
{
value = Convert.ToString(prop.Value);
// Get the printer property corresponding to the current property name
pi = printer.GetType().GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (pi != null)
{
// Set the value of the current property through reflextion
pi.SetValue(printer, value, null);
}
}
}
}
}</pre><br />
<h2>Consuming with ColdFusion</h2><p>Getting the information from the web service and into ColdFusion readable format is as easy as calling any other web service. First you create a web service object and point it to the WSDL URL:</p><pre class="brush: coldfusion" name="code">&lt;cfset systemInfoService = createObject(&quot;webservice&quot;, &quot;http://webapps/systemInfo/systemInfo.asmx?WSDL&quot;) /&gt;</pre><p> <br />
Next, you call the <strong><em>getPrinters()</em></strong> function to get a list of all the printers installed on the computer:</p><pre class="brush: coldfusion" name="code">&lt;cfset printersArray = systemInfoService.getPrinters().getPrinter() /&gt;</pre><p> <br />
the <strong><em>getPrinter()</em></strong> on the end returns an array of printer objects:</p><p><a href="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/getPrintersCFDump.png"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="620" alt="getPrintersCFDump" src="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/getPrintersCFDump_thumb.png" width="449" border="0" /></a> </p><p>If you like to filter the list of printers and get only the printers that are installed on a different server but shared locally, you just need to pass a string containing part of the server name:</p><pre class="brush: coldfusion" name="code">&lt;cfset printersArray = systemInfoService.getPrintersWithNameFilter(nameFilter = &quot;\\ntserver_hq5&quot;).getPrinter() /&gt;</pre><p> <br />
Where &quot;ntserver_hq5&quot; is the name of the server where the printer is actually installed. For network printers installed locally, WMI returns the server name as part of the printer name.</p><p>Once you perform the web service call, you simply output the printers array as follows:</p><pre class="brush: coldfusion" name="code">&lt;cfoutput&gt;
&lt;ul&gt;
&lt;cfloop from=&quot;1&quot; to=&quot;#arraylen(printersArray)#&quot; index=&quot;i&quot;&gt;
&lt;li&gt;
#printersArray[i].getName()#
&lt;ol&gt;
&lt;li&gt;
Location: #printersArray[i].getLocation()#
&lt;/li&gt;
&lt;li&gt;
ShareName: #printersArray[i].getShareName()#
&lt;/li&gt;
&lt;li&gt;
Description: #printersArray[i].getDescription()#
&lt;/li&gt;
&lt;li&gt;
Caption: #printersArray[i].getCaption()#
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/cfloop&gt;
&lt;/ul&gt;
&lt;/cfoutput&gt;</pre><a href="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/getPrintersWithNameFilterOutput.png"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="118" alt="getPrintersWithNameFilterOutput" src="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/getPrintersWithNameFilterOutput_thumb.png" width="363" border="0" /></a> <br />
<h2>Implementation Details</h2><p>The service returns four properties for each printer. Here is an example XML:</p><pre class="brush: xml" name="code">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;systemData xmlns:xs=&quot;http://webapps/systemInfo&quot;&gt;
&lt;printer&gt;
&lt;name&gt;t&lt;/name&gt;
&lt;location&gt;t&lt;/location&gt;
&lt;shareName&gt;t&lt;/shareName&gt;
&lt;description&gt;t&lt;/description&gt;
&lt;caption&gt;t&lt;/caption&gt;
&lt;/printer&gt;
&lt;printer&gt;
&lt;name&gt;t&lt;/name&gt;
&lt;location&gt;t&lt;/location&gt;
&lt;shareName&gt;t&lt;/shareName&gt;
&lt;description&gt;t&lt;/description&gt;
&lt;caption&gt;t&lt;/caption&gt;
&lt;/printer&gt;
&lt;/systemData&gt;</pre><p> <br />
WMI actually returns quite a few properties for each printer. However, I chose only the <strong>Name</strong>, <strong>Location</strong>, <strong>ShareName</strong>, <strong>Description</strong> and <strong>Caption </strong>as relevant.</p><p>Once the sample XML is defined, Visual Studio makes it pretty easy to generate a XML Schema from your sample XML by going to the &quot;XML&quot; menu and clicking on &quot;Create Schema&quot;</p><p><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="484" alt="createXMLSchema" src="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/createXMLSchema.jpg" width="546" border="0" /> </p><p>You end up with the following schema:</p><pre class="brush: xml" name="code">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;schema xmlns:xs=&quot;http://webapps/systemInfo&quot; attributeFormDefault=&quot;unqualified&quot; elementFormDefault=&quot;qualified&quot; xmlns=&quot;http://www.w3.org/2001/XMLSchema&quot;&gt;
&lt;element name=&quot;systemData&quot;&gt;
&lt;complexType&gt;
&lt;sequence&gt;
&lt;element maxOccurs=&quot;unbounded&quot; name=&quot;printer&quot;&gt;
&lt;complexType&gt;
&lt;sequence&gt;
&lt;element name=&quot;name&quot; type=&quot;string&quot; /&gt;
&lt;element name=&quot;location&quot; type=&quot;string&quot; /&gt;
&lt;element name=&quot;shareName&quot; type=&quot;string&quot; /&gt;
&lt;element name=&quot;description&quot; type=&quot;string&quot; /&gt;
&lt;element name=&quot;caption&quot; type=&quot;string&quot; /&gt;
&lt;/sequence&gt;
&lt;/complexType&gt;
&lt;/element&gt;
&lt;/sequence&gt;
&lt;/complexType&gt;
&lt;/element&gt;
&lt;/schema&gt;</pre><p> <br />
To convert the schema to a C# class so we can populate it at runtime, serialize it and return it we can use the xsd.exe utility with the following arguments:</p><pre class="brush: xml" name="code">C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\xsd.exe &quot;path\to\schema.xsd&quot; /classes /nologo /out:&quot;path/to/output.cs&quot;</pre><p> <br />
This way we end up with a serializable C# class which can be populated at runtime and still be serialized as our example XML. I called mine <strong><em>systemData </em></strong>and used it above as:</p><pre class="brush: csharp" name="code">// Create new instance of the systeData class
systemData systemDataInstance = new systemData();
// Create a new instance of the systemDataPrinter class
systemDataPrinter printer = null;
// Create an array of printer objects to store the printer data
systemDataInstance.printer = new systemDataPrinter[moc.Count];</pre><p> <br />
where <strong><em>moc.Count </em></strong>is the number of WMI <strong><em>ManagementObject </em></strong>that was returned for the WMI query<strong><em>.</em></strong></p><p>The last point worth mentioning is inside the <strong><em>saveProperties() </em></strong>function. The function takes an array list of properties, loops over them and for each one sets the according property in the systemDataPrinter class. It does so by using reflextion:</p><pre class="csharp" name="code">// Get the printer property corresponding to the current property name
pi = printer.GetType().GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (pi != null)
{
// Set the value of the current property through reflextion
pi.SetValue(printer, value, null);
}</pre><h2>Gotchas</h2><ol><li>To get network printers listed you have to have the printer installed on the machine under a local account and configure the web service to use the account when performing the query. You use the &quot;<strong><em>impersonate</em></strong>&quot; tag and specify the user and the password: <br />
<br />
<pre class="brush: xml" name="code">&lt;identity impersonate=&quot;true&quot; userName=&quot;user@domain.com&quot; password=&quot;password&quot; /&gt;</pre><br />
</li>
<li>Once you use xsd.exe to generated the C# serializable class, you have to edit it and add the <strong><em>namespace</em></strong> for the root node attribute: <br />
<br />
<pre class="brush: csharp" name="code">[System.Xml.Serialization.XmlRootAttribute(Namespace = &quot;http://webapps/systemInfo&quot;, IsNullable = false)]
</pre></li>
<li>The web service has to be setup in IIS by creating an application and setting the directory security to allow anonymous access. <br />
<a href="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/iisCreateApplication.png"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="238" alt="iisCreateApplication" src="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/iisCreateApplication_thumb.png" width="244" border="0" /></a> <a href="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/iisSetDirectorySecurity.png"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="145" alt="iisSetDirectorySecurity" src="http://tech-cats.net/blog/images/HeyWMIWhereCanIPrint_C69A/iisSetDirectorySecurity_thumb.png" width="244" border="0" /></a> </li>
<li>The web service has to be configured to allow calls through HTTP get inside the <strong><em>Web.config</em></strong>: <br />
<br />
<pre class="brush: xml" name="code">&lt;webServices&gt;
&lt;protocols&gt;
&lt;add name=&quot;HttpGet&quot;/&gt;
&lt;/protocols&gt;
&lt;/webServices&gt;</pre></li>
</ol><h2>Downloads</h2><p>Web Service Implementation: <a title="systemInfo.printer.zip" href="http://tech-cats.net/blog/downloads/dotnet/systemInfo.printers.zip" target="_blank">systemInfo.printer.zip</a></p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-82338473494558940302008-03-24T15:34:00.001-04:002008-03-24T15:34:21.040-04:00Simple Logging in Your .NET Application<p>Logging can be a very useful tool when developing any application. Furthermore, logging is a must when deploying applications to a production environment. Keeping track of errors and informational messages can help you greatly reduce the time it takes to debug and fix a problem. As in any language, there is more than one way to enable logging in .NET. This article will cover getting started with logging using the free <a title="http://logging.apache.org/log4net" href="http://logging.apache.org/log4net/" target="_blank">log4net</a> framework.</p> <h2>To Get Started</h2> <ol> <li>Get log4net from <a title="http://logging.apache.org/log4net/download.html" href="http://logging.apache.org/log4net/download.html" target="_blank">http://logging.apache.org/log4net/download.html</a> </li> <li>Create a sample .NET application that and call it &quot;sampleLoggingApp&quot; </li> <li>Create a place to store the log4net DLL, I usually create a &quot;dependencies&quot; directory </li> <li>Copy the extracted log4net DLL from &quot;bin\net\2.0\release\log4net.dll&quot; to your &quot;dependencies&quot; directory </li> <li>Set your Visual Studio to show all files, include the log4net DLL in your project and add a reference to it in your project </li> </ol> <h2>The Fun Stuff</h2> <p><strong>Configuration</strong></p> <ol> <li>Create your log4net configuration file, we will call it log4net.config to keep appending to the same file: <br /> <br /> <pre class="xml" name="code">&lt;log4net&gt;
&lt;appender name=&quot;RollingLogFileAppender&quot; type=&quot;log4net.Appender.RollingFileAppender&quot;&gt;
&lt;file value=&quot;sampleLog.log&quot; /&gt;
&lt;layout type=&quot;log4net.Layout.PatternLayout&quot;&gt;
&lt;conversionPattern value=&quot;%date [%thread] %-5level %logger [%ndc] - %message%newline&quot; /&gt;
&lt;/layout&gt;
&lt;/appender&gt;
&lt;root&gt;
&lt;level value=&quot;DEBUG&quot; /&gt;
&lt;appender-ref ref=&quot;RollingLogFileAppender&quot; /&gt;
&lt;/root&gt;
&lt;/log4net&gt;</pre>
<br /></li>
<li>Set the path to the log4net.config file:
<br />
<br />
<pre class="csharp" name="code">private static String m_log4netConfigFile = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), &quot;log4net.config&quot;);</pre>
<br />This sets the path of the log4net.config file to be in the same directory are your executable.
<br />
<br /></li>
<li>Create a static log instance:
<br />
<pre class="csharp" name="code">private static readonly ILog m_log = LogManager.GetLogger(typeof(Form1));</pre>
<br />Where &quot;Form1&quot; is the name of your application instance.
<br /></li>
<li>Set a build event in Visual Studio that will copy the log4net configuration file in the output directory on every build:
<br />
<br />
<pre class="csharp" name="code">xcopy /y &quot;$(ProjectDir)log4net.config&quot; &quot;$(TargetDir)&quot;</pre>
<br /></li>
<li>Write some information to the log:
<br />
<br />
<pre class="csharp" name="code">m_log.Debug(&quot;Inside Form1_Load&quot;);</pre>
<br /></li>
</ol>
<h2>More Information</h2>
<p>I have covered the basics of getting started. However, log4net has quite a few more possible configurations such as</p>
<ul>
<li>Log to a database table </li>
<li>Log to the console </li>
<li>Log to the event log </li>
<li>Log to an email address </li>
</ul>
<p>More information for each configuration can be found at <a title="http://logging.apache.org/log4net/release/config-examples.html" href="http://logging.apache.org/log4net/release/config-examples.html" target="_blank">http://logging.apache.org/log4net/release/config-examples.html</a></p>
<h2>Downloads</h2>
<p><a title="sampleLoggingApp.zip" href="http://tech-cats.net/blog/examples/dotnet/sampleLoggingApp.zip" target="_blank">sampleLoggingApp.zip</a></p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com3tag:blogger.com,1999:blog-7367794905551891931.post-668646841443866412008-03-20T14:34:00.001-04:002008-03-20T14:34:37.672-04:00Model-Glue Tips and Tricks<ol> <li> <h2><b>Turn off automatic reloading of your application</b> </h2> <br />This will greatly reduce the time the application needs to process all request. This is the way I develop Model-Glue applications. In your Model-Glue configuration (the &quot;<b><i>modelGlueConfiguration</i></b>&quot; bean in ColdSpring.xml), look for the &quot;<b><i>reload</i></b>&quot; property and set it to false: <br /> <br /> <pre class="xml" name="code">&lt;property name=&quot;reload&quot;&gt;<br /> &lt;value&gt;false&lt;/value&gt;<br />&lt;/property&gt;</pre>
<br />Keep in mind that changes made to anything else but your views will require an application reload. For my approach to that, read the next tip. </li>
<li>
<h2><b>Use two browsers</b> </h2>
<br />The basics of this point has to do with using one browser for your development and one for reloading the application. I suggest this since if your application keep session state, reloading the application from the same browser will reset the session while reloading the application from a different browser will not. If you turn the Model-Glue automatic reload, your application will run faster but you will not be able to see any changes made in your controllers or model. Once you made a change you will need to manually reload the application by invoking the &quot;<b><i>reloadKey</i></b>&quot; with the &quot;<b><i>reloadPassword</i></b>&quot; as defined in the ColdSpring.xml file. By default the &quot;<b><i>reloadKey</i></b>&quot; is set to &quot;<b><i>init</i></b>&quot; and the &quot;<b><i>reloadPassword</i></b>&quot; is set to &quot;true&quot;. So you would reload by invoking &quot;<strong><em>index.cfm?init=true</em></strong>&quot;. To take this further I add the following code to my <b><i>onRequestStart</i></b> inside Application.cfc to reload the application if you simply go to <b><i>&quot;/yourApplication/?init</i></b>&quot;:
<br />
<br />
<pre class="cf" name="code">&lt;cffunction name=&quot;OnRequestStart&quot; output=&quot;no&quot;&gt;<br /> &lt;cfif structKeyExists(url, &quot;init&quot;)&gt;<br /> &lt;cfset structclear(Application) /&gt;<br /> &lt;cfset structclear(Session) /&gt;<br /><br /> &lt;cfset onApplicationStart() /&gt;<br /> &lt;/cfif&gt;<br />&lt;/cffunction&gt;<br /></pre>
</li>
<li>
<h2><b>Turn on debugging</b> </h2>
<br />Debugging is enabled by default but to turn it on or off you change the &quot;true/false&quot; value inside the &quot;modelGlueConfiguration&quot; bean in your ColdSpring.xml&quot; file:
<br />
<br />
<pre class="xml" name="code">&lt;property name=&quot;debug&quot;&gt;<br /> &lt;value&gt;true&lt;/value&gt;<br />&lt;/property&gt;<br /></pre>
</li>
<li>
<h2><b>Use a custom ColdSpring bean for your configuration settings</b> </h2>
<br />To store your own configuration values to be used throughout the application you can take advantage of Model-Glues &quot; <strong><em>SimpleConfig</em></strong> bean. This bean configuration will go in your ColdSpring.xml file and look something like:
<br />
<br />
<pre class="xml" name="code">&lt;bean id=&quot;applicationConfiguration&quot; class=&quot;ModelGlue.Bean.CommonBeans.SimpleConfig&quot;&gt;<br />&lt;property name=&quot;config&quot;&gt;<br />&lt;map&gt;<br /> &lt;!-- The network path where the bartender templates are stored --&gt;<br /> &lt;entry key=&quot;networkPathToLabelTemplates&quot;&gt;<br /> &lt;value&gt;\\larry\indium\Production\labelGeneration&lt;/value&gt;<br /> &lt;/entry&gt;<br />&lt;/map&gt;<br />&lt;/property&gt;<br />&lt;/config&gt;<br /></pre>
<br />To access your <b><i>applicationConfiguration</i></b> bean, you would use:
<br />
<pre class="cf" name="code">getModelGlue().getBean(&quot;applicationConfiguration&quot;, true)</pre>
<br />I usually do this inside the &quot;<b><i>init</i></b>&quot; method of my controller:
<br />
<pre class="cf" name="code">&lt;cfset variables.appConfig = getModelGlue().getBean(&quot;applicationConfiguration&quot;, true) /&gt;</pre>
<br />And use it later on with:
<br />
<br />
<pre class="cf" name="code">&lt;!--- Get the path to the label templates from the configuration ---&gt;<br />&lt;cfset var networkPathToLabelTemplates = variables.appConfig.getConfigSetting(&quot;networkPathToLabelTemplates&quot;) /&gt;</pre>
</li>
<li>
<h2><b>Take advantage of an ORM framework such as Reactor</b> </h2>
<br />Initial versions Model-Glue used to come with Reactor but do no longer. I found it is a little difficult for a novice to start using Reactor with Model-Glue. To take advantage of Reactor, you have to <a href="http://trac.reactorframework.com" target="_blank">download</a> it, unzip it and create a ColdFusion mapping called &quot;reactor&quot; that points to your Reactor directory. Next comes the Model-Glue configuration. You need to add the following bean definitions to your ColdSpring.xml:
<br />
<br />
<pre class="xml" name="code">&lt;bean id=&quot;ormAdapter&quot; class=&quot;ModelGlue.unity.orm.ReactorAdapter&quot;&gt;
&lt;constructor-arg name=&quot;framework&quot;&gt;
&lt;ref bean=&quot;ModelGlue&quot; /&gt;
&lt;/constructor-arg&gt;
&lt;/bean&gt;
&lt;bean id=&quot;ormService&quot; class=&quot;reactor.reactorFactory&quot;&gt;
&lt;constructor-arg name=&quot;configuration&quot;&gt;
&lt;ref bean=&quot;reactorConfiguration&quot; /&gt;
&lt;/constructor-arg&gt;
&lt;/bean&gt;</pre>
<br />
<br />And of course you need the &quot;reactorConfiguraiton&quot; bean:
<br />
<pre class="xml" name="code">&lt;bean id=&quot;reactorConfiguration&quot; class=&quot;reactor.config.config&quot;&gt;<br /> &lt;constructor-arg name=&quot;pathToConfigXml&quot;&gt;<br /> &lt;value&gt;/labelGeneration/config/Reactor.xml&lt;/value&gt;<br /> &lt;/constructor-arg&gt;<br /><br /> &lt;property name=&quot;project&quot;&gt;<br /> &lt;value&gt;labelGeneration&lt;/value&gt;<br /> &lt;/property&gt;<br /><br /> &lt;property name=&quot;dsn&quot;&gt;<br /> &lt;value&gt;labelGeneration&lt;/value&gt;<br /> &lt;/property&gt;<br /><br /> &lt;property name=&quot;type&quot;&gt;<br /> &lt;value&gt;mssql&lt;/value&gt;<br /> &lt;/property&gt;<br /><br /> &lt;property name=&quot;mapping&quot;&gt;<br /> &lt;value&gt;/labelGeneration/model&lt;/value&gt;<br /> &lt;/property&gt;<br /><br /> &lt;property name=&quot;mode&quot;&gt;<br /> &lt;value&gt;production&lt;/value&gt;<br /> &lt;/property&gt;<br />&lt;/bean&gt;<br /></pre>
Now you can take advantage of Model-Glue's built in scaffolding and generic messages (<b><i>genericList</i></b>, <b><i>genericRead</i></b>, <b><i>genericCommit</i></b> and <b><i>genericDelete</i></b>). </li>
<li>
<h2><b>Use event beans</b> </h2>
<br />Event bean is a predefined CFC that will store values form your form. It works by creating a one to one relationship between your form values and getters/setters in your CFC. Once you have your event bean created you can populate it from everything in the form scope by using the <b><i>makeEventBean</i></b> function:
<br />
<br />
<pre class="cf" name="code">&lt;!--- Create an instance of the bean ---&gt;<br />&lt;cfset var completePrintJobFormBean = getModelGlue().getBean(&quot;completePrintJobFormBean&quot;) /&gt;<br /><br />&lt;cfset arguments.event.makeEventBean(completePrintJobFormBean) /&gt;<br /><br />&lt;!--- Trace the label data id field ---&gt;<br />&lt;cfset arguments.event.trace(&quot;labelDataID&quot;, completePrintJobFormBean.getLabelDataID()) /&gt;</pre>
<br />Creating event beans has to do with defining getters/setters and local variables for each one of your form fields. It can be a tedious process but thankfully there is the <a title="Rooibos Generation" href="http://rooibos.maestropublishing.com/" target="_blank">Rooibos Generation</a> to do it for you by simply taking the names of your form fields and generating all the code for your. You can learn more about this at Dan Wilson's blog article about <a title="So you want to create a ModelGlue:Unity application? ( Part 3 )" href="http://www.nodans.com/index.cfm/2007/1/21/So-you-want-to-create-a-ModelGlueUnity-application--Part-3-#more">So you want to create a ModelGlue:Unity application? ( Part 3 )</a>.
<br />
<br />To use your newly generated bean, you will have to add the bean definition to your ColdSpring.xml file:
<br />
<br />
<pre class="xml" name="code">&lt;bean id=&quot;completePrintJobFormBean&quot;<br /> class=&quot;labelGeneration.model.completePrintJobFormBean&quot;<br /> singleton=&quot;false&quot; /&gt;</pre>
<br />Keep in mind that if your form changes, you have to regenerate the bean. If you do not care about reusing your beans through external consumers, such as Flex, I have written about an alternative approach in &quot;<a title="Get All Form Fields in ModelGlue the Elegant Way" href="/news/get-all-form-fields-modelglue-" target="_blank">Get All Form Fields in ModelGlue the Elegant Way</a>&quot;. </li>
<li>
<h2><b>Use tracing to debug the value of your variables</b> </h2>
<br />Once you have debugging enabled, you can add values to your debug output by using the event.trace() method:
<br />
<br />
<pre class="cf" name="code">&lt;cfset arguments.event.trace(&quot;myVariableValue&quot;, myVariable) /&gt;</pre>
</li>
<li>
<h2>Split your configuration in multiple files </h2>
<br />You can tell ModelGlue to include different configuration files by using the &quot;include template&quot; directive. I use this approach to separate my events based on the action they are related to:
<br />
<br />
<pre class="xml" name="code">&lt;modelglue&gt;<br /> &lt;include template=&quot;./config/events/labelEvents.xml&quot;/&gt;<br /> ....<br />&lt;/modelglue&gt;</pre>
<br />Here is what the included files looks like:
<br />
<pre class="xml" name="code">&lt;modelglue&gt;<br />&lt;event-handlers&gt;<br /><br />&lt;event-handler name=&quot;overRideLabelData&quot;&gt;<br /> &lt;views&gt;<br /> &lt;view name=&quot;body&quot; template=&quot;frmOverRideLabelData.cfm&quot; /&gt;<br /> &lt;/views&gt;<br /><br /> &lt;results&gt;<br /> &lt;result do=&quot;strippedApplicationTemplate&quot; /&gt;<br /> &lt;/results&gt;<br />&lt;/event-handler&gt;<br /><br />&lt;/event-handlers&gt;<br />&lt;/modelglue&gt; </pre>
</li>
</ol> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-39285901618883032432008-03-17T15:45:00.001-04:002008-03-17T16:01:34.964-04:00Querying the File System - From ColdFusion to SQL Server<p>While looking for a solution of a different problem, I realized that SQL Server has a feature called extended stored procedures. Extended stored procedures, at least in SQL Server 2000, have to be written in C++ and compiled to a DLL. A good example of such a procedure is my previous article on <a href="http://blog.tech-cats.com/2007/09/using-regular-expression-in-sql-server.html">Using Regular Expression in SQL Server</a>. I am not going to get into how to write extended stored procedures in this article since my C++ skills are rusty at best. Instead, I will talk about using an existing stored procedure to query the file system and how to move from using ColdFusion to using SQL Server.</p> <h3>Using ColdFusion</h3> <p>Getting a list of files is pretty trivial in ColdFusion. All you have to do is use the &quot;&lt;cfdirectory&gt;&quot; tag like so:</p> <pre class="cf" name="code">&lt;cfset localDirectoryPath = &quot;C:\Temp&quot; /&gt;
&lt;cfdirectory
name=&quot;textFiles&quot;
action=&quot;list&quot;
filter=&quot;*.txt&quot;
directory=&quot;#localDirectoryPath#&quot; /&gt;</pre>
<p>
<br />That will get a list of all files with the &quot;.txt&quot; extension in the &quot;C:\Temp&quot; directory.</p>
<p>Getting a list of files from a network drive is not any different except for specifying the networks path as UNC path (and a little service configuration):</p>
<pre class="cf" name="code">&lt;cfset networkDirectoryPath = &quot;\\larry\share&quot; /&gt;
&lt;cfdirectory
name=&quot;textFiles&quot;
action=&quot;list&quot;
filter=&quot;*.txt&quot;
directory=&quot;#networkDirectoryPath#&quot; /&gt;</pre>
<p>
<br />As stated above, this will not work if you do not have the ColdFusion service configured to use a specific account that has access to your network path.</p>
<p>The typical ColdFusion service setup looks like the following screen shot when accessed through the &quot;Services&quot; configuration in Windows:</p>
<p><a href="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/coldFusionServiceNoUser.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="244" alt="coldFusionServiceNoUser" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/coldFusionServiceNoUser_thumb.jpg" width="216" border="0" /></a> </p>
<p>To be able to query a network path, you need to specify a user that has access to that network path:</p>
<p><a href="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/coldFusionServiceUser.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="244" alt="coldFusionServiceUser" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/coldFusionServiceUser_thumb.jpg" width="216" border="0" /></a> </p>
<h3>Using SQL Server</h3>
<p>The setup for using SQL Server for querying network paths is the same as the one for ColdFusion:</p>
<p>Default Setup <a href="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/sqlServerServiceNoUser.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="244" alt="sqlServerServiceNoUser" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/sqlServerServiceNoUser_thumb.jpg" width="216" border="0" /></a> But should be something like <a href="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/sqlServerServiceUser.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="244" alt="sqlServerServiceUser" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/sqlServerServiceUser_thumb.jpg" width="216" border="0" /></a> </p>
<p>If you do not setup the SQL Server to run as the appropriate user, trying to query a network path with the method provided here, you will an access denied error:</p>
<p><a href="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/sqlServerAccessDenied.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="52" alt="sqlServerAccessDenied" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/sqlServerAccessDenied_thumb.jpg" width="181" border="0" /></a> </p>
<p>With that aside, I have written a custom stored procedures based on the built-in extended stored procedure &quot;xp_cmdshell&quot;. This procedure will return a query with the following:</p>
<ul>
<li>fileID - unique auto incremented integer value </li>
<li>fileName - the name of the file </li>
<li>lastModifiedOn - the date the file was last modified </li>
<li>fileSize - the size of the file in bytes </li>
</ul>
<p>Here are the results of querying the local path &quot;C:\Temp&quot; without a file extensions filter:</p>
<pre class="sql" name="code">exec dbo.getDirectoryFileList 'c:\temp', null</pre>
<p><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="390" alt="queryingFileSystemNoFilter" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/queryingFileSystemNoFilter.jpg" width="698" border="0" /> </p>
<p>Once configured the procedure can also work on network drives like so to return only files with the &quot;.btw&quot; extension:</p>
<pre class="sql" name="code">exec dbo.getDirectoryFileList '\\larry\indium\Production\labelGeneration', '*.btw'</pre>
<p><a href="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/queryingFileSystemWithFilter.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="104" alt="queryingFileSystemWithFilter" src="http://tech-cats.net/blog/images/QueryingtheFileSystemwithSQLServer_DC43/queryingFileSystemWithFilter_thumb.jpg" width="644" border="0" /></a> </p>
<h3>Show Me the Code</h3>
<pre class="sql" name="code">/*
Type: Stored Procedure
Name: dbo.getDirectoryFileList
Author: Boyan Kostadinov
Created: 03.17.2008
Dependencies: master.dbo.xp_cmdshell
Usage: exec dbo.getDirectoryFileList 'c:\temp', null
exec dbo.getDirectoryFileList '\\larry\indium\Production\labelGeneration', '*.btw'
Parameters: @directoryPath varchar(255)
- The path of the local or network directory
@fileExtensionFilter varchar(10) - Optional
- The file extension to filter the file list by
Returns: A list of files found on the file system
*/
create procedure dbo.getDirectoryFileList
@directoryPath varchar(255),
@fileExtensionFilter varchar(10) = null
as
set nocount on
-- Declare and initialize local variables
declare @dosCommand varchar(5000)
set @dosCommand = ''
-- If the file extension fileter was empty, set it to all files
if @fileExtensionFilter is null or ltrim(rtrim(@fileExtensionFilter)) = ''
set @fileExtensionFilter = '*.*'
-- If the directory path does not have an ending '\', append one
if substring(@directoryPath, len(@directoryPath), 1) &lt;&gt; '\'
set @directoryPath = @directoryPath + '\'
-- Build the dos command to get a list of files
select @dosCommand =
'insert into #tempFileList(fileListRow) ' +
'exec master.dbo.xp_cmdshell ''dir ' + @directoryPath + + @fileExtensionFilter + ''''
-- Create a temporary table to store the file list
create table #tempFileList (
fileListRow varchar(1000) null
)
-- Create the #fileList temporary table to store the file list
create table #fileList (
fileID int primary key identity(1,1) not null,
[fileName] varchar(255) not null,
lastModifiedOn datetime not null,
fileSize bigint not null,
)
exec(@dosCommand)
-- 8 - Delete unneeded data from the #OriginalFileList
delete from #tempFileList
where fileListRow is null
delete from #tempFileList
where fileListRow like '%Volume%'
delete from #tempFileList
where fileListRow like '%Directory%'
delete from #tempFileList
where fileListRow like '%&lt;DIR&gt;%'
delete from #tempFileList
where fileListRow like '%bytes%'
if not exists (select * from #tempFileList where fileListRow like '%access is denied%')
begin
-- Populate the #fileList table with the final data
insert into #fileList(lastModifiedOn, fileSize, [fileName])
select ltrim(substring(fileListRow, 1, 10))
+
' '
+
rtrim(ltrim(substring(fileListRow, 11, 15)))
+
'm'
as 'lastModifiedOn',
replace(ltrim(substring(fileListRow, 21, 18)), ',', '') as 'fileSize',
ltrim(substring(fileListRow, 40, 1000)) as 'fileName'
from #tempFileList
select * from #fileList
end
else
select fileListRow as errorMessage from [#tempFileList] as e
-- Drop the temporary tables
drop table #tempFileList
drop table #fileList
set nocount off
go</pre>
<h3>Download</h3>
<p>You can download the stored procedure from <a title="http://tech-cats.net/blog/downloads/sql/getDirectoryFileList.txt" href="http://tech-cats.net/blog/downloads/sql/getDirectoryFileList.txt" target="_blank">http://tech-cats.net/blog/downloads/sql/getDirectoryFileList.txt</a><strong></strong></p>
<h3><strong>References</strong> </h3>
<p><a title="Accessing the Windows File System from SQL Server" href="http://www.mssqltips.com/tip.asp?tip=1263" target="_blank">Accessing the Windows File System from SQL Server</a></p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com0tag:blogger.com,1999:blog-7367794905551891931.post-37622109432462924492008-03-11T15:46:00.001-04:002008-03-11T16:00:06.268-04:00Object Oriented Programming with Prototype.js<p>Object oriented programming has been around for quite some time and was made popular by C++ back in the day. Nowadays, even web scripting languages support the paradigm. JavaScript does not have true OO but allows you to use the design pattern in your code. Today, we will dive into using object oriented programming with the popular JavaScript framework Prototype.</p> <p>Prototype makes it easy to declare your own objects by using the "Class.create()" method:</p><pre class="js" name="code">var sampleObject = Class.create();
</pre>
<p><br>Once you do that you can start writing your class specific methods and properties inside the "prototype" object:</p><pre class="js" name="code">sampleObject.prototype = {
}
</pre>
<p><br>To declare private properties you simply specify the name of the property followed by a colon and the value:</p><pre class="js" name="code">sampleObject.prototype = {
linkIDs: ['mainPageLink', 'cdmScreenLink', 'adminLink', 'helpLink'],
statusMessage: '',
currentLinkID: 'myLink'
}
</pre>
<p><br>The important thing to remember is that each property needs to be separated from the next by a comma. As shown above, property values can be a array, a number, a string but cannot be empty value. While:</p><pre class="js" name="code">currentLinkID: 'myLink',
</pre>
<p><br>is valid while</p><pre class="js" name="code">currentLinkID:,
</pre>
<p><br>is not.</p>
<p>Declaring your own functions is not any different. Before we look into functions however, there is one function that requires special attention: the "initialize" function:</p><pre class="js" name="code">initialize: function() {
}
</pre>
<p><br>The initialize function is what JavaScript calls automatically when you create an instance of your object. If you are familiar with OO, this function is the constructor of your object. Any setup and initial requirements for using your object should be done in here.</p>
<p>So to get back to regular functions, they are declared in the format functionName: function() {} as in:</p><pre class="js" name="code">doSomething: function() {
}
</pre><br>
<p>So far your object should like like this:</p><pre class="js" name="code">var sampleObject = Class.create();
sampleObject.prototype = {
linkIDs: ['mainPageLink', 'cdmScreenLink', 'adminLink', 'helpLink'],
statusMessage: '',
currentLinkID: 'myLink',
initialize: function() {
},
doSomething: function() {
}
}
</pre>
<p><br>Big deal right, including that in your html page and/or a separate JavaScript file does not do anything for you. The next step in making it of any use is to actually create an instance of the object like so:</p><pre class="js" name="code">var sampleObjectInstance = new sampleObject();
</pre>
<p><br>Creating an instance of the object automatically calls all your code inside the "initialize" function. Here, a good practice is to wrap the creation of your object inside the windows load or dom:loaded (Prototype v1.6) event like:</p><pre class="js" name="code">Event.observe(window, 'load', function() {
var sampleObjectInstance = new sampleObject();
});
</pre>
<p><br>Or</p><pre class="js" name="code">document.observe("dom:loaded", function() {
var sampleObjectInstance = new sampleObject();
});
</pre>
<p><br>To expand on using properties inside your object, whenever you want to access a property such as "currentLinkID", you have to prefix it with "this" as in:</p><pre class="js" name="code">this.statusMessage = 'Who Am I?';
</pre>
<p><br>That is because inside your object's function, without "this", the code does not know about the property. The same applies to using function so you cannot simply called the "doSomething" function with:</p><pre class="js" name="code">doSomething();
</pre>
<p><br>but instead if you have to use:<br><pre class="js" name="code">this.doSomething();</pre>
<p></p>
<p><br>This can get a little more complicated when it comes to using event listeners inside your code. Let me elaborate. To tie an event observer that will call the "doSomething" function when a users clicks the link with ID 'myLink', you would usually do:</p><pre class="js" name="code">$(this.currentLinkID).observe('click', this.doSomething);
</pre>
<p><br>While that will work, if you try to access any class properties (such as "statusMessage") inside the "doSomething" function, you will get "undefined" for their values. That is because, again as pointed out above, the function is not aware that it belongs to an object so it does not know that the object has properties. The remedy is simple, simply append .bind(this) to the function when it is tied to the event as in:</p><pre class="js" name="code">$(this.currentLinkID).observe('click', this.doSomething.bind(this));
</pre>
<p><br>A similar approach needs to be applied when using the prototype built-in Ajax object. If you want to tie your custom functions to the "onFailure", "onComplete" or "onSuccess" functions, you need to use the "bindAsEventListener" function:</p><pre class="js" name="code">onSuccess: this.showContent.bindAsEventListener(this)
</pre>
<p><br>"Bind" also needs to be used whenever you employ the "each" construct as described in <a title="" href="http://ajax.dzone.com/tips/using-bind-arrays-gotcha-under" target="_blank">Gotcha with Prototype.Bind and Arrays</a></p>
<p>Below is the full class:</p><pre class="js" name="code">var sampleObject = Class.create();
sampleObject.prototype = {
linkIDs: ['mainPageLink', 'cdmScreenLink', 'adminLink', 'helpLink'],
statusMessage: '',
currentLinkID: 'myLink',
initialize: function() {
this.statusMessage = 'Who Am I?';
$(this.currentLinkID).observe('click', this.doSomething.bind(this));
},
doSomething: function() {
alert(this.statusMessage);
new Ajax.Request(
$(this.currentLinkID).href,
{
method: 'get',
onSuccess: this.processContent.bindAsEventListener(this),
evalScripts: true
}
);
},
processContent: function(request) {
}
}
Event.observe(window, 'load', function() {
var sampleObjectInstance = new sampleObject();
});
</pre>
<p><br>To call the functions of the sampleObject from outside you would simply do:</p><pre class="js" name="code">sampleObjectInstance.doSomething();
</pre>
<p>While you can access it's properties with the syntax:</p><pre class="js" name="code">alert(sampleObjectInstance.statusMessage);
</pre><br>
<p>That concludes the basic guide to using object oriented programming with Prototype. Did I miss anything to get you started?</p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com3tag:blogger.com,1999:blog-7367794905551891931.post-37415270177007369212008-03-06T18:53:00.003-05:002009-04-17T12:12:54.673-04:00How to Extract Video Still Frames with MPlayer<p>While working on a video content management system, I was in need of capturing frames from video files so they can be used as a preview for video. The system already had some code in place but it only worked for video encoding in Windows Media format (.wmv extension). That would not do, I thought, not in this day and age when we have so many file formats and video codecs. So I need to able to:</p> <ol> <li>Capture video frames from within .NET code <li>Capture images from all kinds of different video formats <li>Not reinvent the wheel while satisfying #1 and #2</li></ol> <h3>Enter MPlayer</h3> <p>Stolen directly from the <a title="" href="http://www.mplayerhq.hu/design7/info.html" target="_blank">Information</a> page of MPlayer, "MPlayer is a movie player which runs on many systems (see the documentation). It plays most MPEG/VOB, AVI, Ogg/OGM, VIVO, ASF/WMA/WMV, QT/MOV/MP4, RealMedia, Matroska, <a href="http://www.nut-container.org">NUT</a>, NuppelVideo, FLI, YUV4MPEG, FILM, RoQ, PVA files, supported by many native, XAnim, and Win32 DLL codecs. You can watch VideoCD, SVCD, DVD, 3ivx, DivX 3/4/5 and even WMV movies..". To add my own description, MPlayer is an open source command line video player.</p> <h3>So What</h3> <p>Metallica has an old song called "So What", great tune for the metal heads in all of us. The answer is simple, MPlayer can do a lot more than play video. It can capture images, stream video over http and even transcode video from one format to another (but the last feature is grounds for another article).</p> <h3>Putting It Together</h3> <p>You will need:</p> <ul> <li>1 download of MPlayer Windows executable from <a title="" href="http://www.mplayerhq.hu/design7/dload.html" target="_blank">http://www.mplayerhq.hu/design7/dload.html</a> <li>1 download of the MPlayer Windows binary codecs package from <a title="" href="http://www.mplayerhq.hu/design7/dload.html" target="_blank">http://www.mplayerhq.hu/design7/dload.html</a> <li>1 MPlayer .NET wrapper provided in this article</li></ul> <h3>Setup</h3> <ol> <li>Extract the mPlayer executable (mplayer.exe) <li>Extract the mPlayer codecs in a directory called "codecs" in the same directory as the mPlayer executable <li>Add a reference to the mPlayerWrapper project or compiled DLL</li></ol> <h3>Usage</h3> <ul> <li>Capture with default arguments<br><br>in C# <br><pre class="csharp" name="code">// Specify the path to the video file
string videoFilePath = @"drive letter:\path\to\myVideo.mpg";
// Declare the mplayer instance with the mplayer executable residing in the
// same directory as your executable
mPlayerWrapper mPlayerInstance = new mPlayerWrapper();
// Capture frames
mPlayerInstance.captureFrames(videoFilePath);
</pre><br>in VB.NET <br><pre class="vb" name="code">' Specify the path to the video file
dim videoFilePath As String = "drive letter:\path\to\myVideo.mpg"
' Declare the mplayer instance with the mplayer executable residing in the
' same directory as your executable
dim mPlayerInstance As new mPlayerWrapper()
' Capture frames
mPlayerInstance.captureFrames(videoFilePath)
</pre><br>This will capture 12 frames with 5 second interval between each frame and put them in the same directory as your video file. The filename of each frame will be "myVideo_thumb01.jpg" to "myVideo_thumb12.jpg". Each frame will be scaled to 270x200.<br>
<li>Capture arguments
<ul>
<li>mPlayerPath - sets the path where the mPlayer executable (mplayer.exe) is located. The default is in the same directory as your code is executing.
<li>currentFilePath - sets the file path to the video file you are using
<li>cleanOutputDirectory - deletes all the "jpg" images in the capture output directory before capturing
<li>captureInterval - sets the interval at which frames will be captured. Only applicable if using a time interval capture method (as outlined below)
<li>numberOfFramesToCapture - the number of frames to be captured
<li>captureExactNumberOfFrames - tells the wrapper to attempt to capture the exact number of frames as specified by the "numberOfFramesToCapture" property. This can be used if a file has too few frames but you still want to capture an exact number
<li>useTimeSeekToCapture - used to set the wrapper method of capture to seeking through the file instead of capturing a frame at an interval
<li>thumbnailPrefix - the prefix to be used when creating the filenames for captured frames. The default is the name of the video with "_thumb" append to it as in "myVideo_thumb01.jpg"
<li>capturedFrameWidthHeight - the width:height that each frame will be scaled to. The default is 270:200
<li>scaleCapturedFrames - used in conjunction with the "capturedFrameWidthHeight" property to scale down the captured frames. Set to true by default</li></ul>
<li>Capture in a different output directory <br>in C# <br><pre class="csharp" name="code">// Specify the path to the video file
string videoFilePath = @"drive letter:\path\to\myVideo.mpg";
// Specify the output directory
string outputPath = @"drive letter:\path\to\output directory";
// Declare the mplayer instance with the mplayer executable residing in the
// same directory as your executable
mPlayerWrapper mPlayerInstance = new mPlayerWrapper();
// Capture frames
mPlayerInstance.captureFrames(videoFilePath, outputPath);
</pre><br>in VB.NET <br><pre class="vb" name="code">' Specify the path to the video file
dim videoFilePath As String = "drive letter:\path\to\myVideo.mpg"
' Specify the output directory
dim outputPath As String = @"drive letter:\path\to\output directory"
' Declare the mplayer instance with the mplayer executable residing in the
' same directory as your executable
dim mPlayerInstance As new mPlayerWrapper()
' Capture frames
mPlayerInstance.captureFrames(videoFilePath, outputDirectory)
</pre>
<li>Using distinct capture methods:
<ol>
<li>captureFramesWithInterval - by default captures a frame every 5 seconds with up to 12 frames
<li>captureFramesWithTimeSeek - by default captures 1 frame each second with up to 12 frames by seeking through the file</li></ol>
<li><strong>Bonus</strong>
<ul>
<li>getFileProperties - returns a SortedList of video and audio properties for the file
<li>getAudioProperties - returns a SortedList of audio properties for the file
<li>getVideoProperties - returns a SortedList of video properties for the file</li></ul></li></ul>
<h3>Downloads</h3>
<p>mPlayerWrapper: <a href="http://blog.tech-cats.net/examples/dotnet/mPlayerWrapper.dll">http://blog.tech-cats.net/examples/dotnet/mPlayerWrapper.dll</a><br>mPlayerWrapper Source: <a href="http://blog.tech-cats.net/examples/dotnet/mplayerWrapper-v0.2.zip">http://blog.tech-cats.net/examples/dotnet/mplayerWrapper-v0.2.zip</a></p>Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com24tag:blogger.com,1999:blog-7367794905551891931.post-79382064529722231542008-03-04T09:19:00.001-05:002008-03-04T09:42:41.926-05:00Top 13 Visual Studio Keyboard Shortcuts<p>My friends in college always made fun of my keyboard obsession. At the time, I knew all the Windows 98 specific shortcut keys and often had no need for the mouse. Keyboard shortcuts still rule in my book. It is amazing that you can get around without using a mouse at all. Master the following Visual Studio shortcuts and your colleagues might stare at you with amazement.</p> <ol> <li><strong>F5: </strong>Start your project in debug mode<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber1.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="146" alt="vsShortcutsNumber1" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber1_thumb.jpg" width="244" border="0"></a> <br> <li><strong>F7 &amp; Shift-F7: </strong>Show the code windows &amp; Show the designer window<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber2.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="154" alt="vsShortcutsNumber2" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber2_thumb.jpg" width="244" border="0"></a> <br> <li><strong>Alt-Enter: </strong>Show the properties panel for a selected object (this is general Windows shortcut that can be used on files and directories)<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber3.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="205" alt="vsShortcutsNumber3" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber3_thumb.jpg" width="244" border="0"></a> <br> <li><strong>F6 / Shift-F6 / Ctrl-Shift-B: </strong>Build solution / Build project / Build solution<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber4.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="147" alt="vsShortcutsNumber4" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber4_thumb.jpg" width="244" border="0"></a>&nbsp;<br> <li><strong>Shift-Alt-C:</strong> Add a new class to your project<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber12.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="156" alt="vsShortcutsNumber12" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber12_thumb.jpg" width="244" border="0"></a>&nbsp;<br> <li><strong>Ctrl-K + <strong>Ctrl</strong>-C: </strong>Comment a selected block of code<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber5.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="88" alt="vsShortcutsNumber5" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber5_thumb.jpg" width="244" border="0"></a> <br> <li><strong><strong>Ctrl</strong>-K + <strong>Ctrl</strong>-U:</strong> Un-comment a selected block of code<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber6.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="79" alt="vsShortcutsNumber6" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber6_thumb.jpg" width="244" border="0"></a> <br> <li><strong><strong><strong>Ctrl</strong></strong>-M + <strong><strong>Ctrl</strong></strong>-O / <strong><strong>Ctrl</strong></strong>-M + <strong><strong>Ctrl</strong></strong>-P:</strong> Collapse all code to definitions / Expand all code (stop collapsing)<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber7.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="100" alt="vsShortcutsNumber7" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber7_thumb.jpg" width="244" border="0"></a> <br> <li><strong><strong>Ctrl</strong>-M + <strong>Ctrl</strong>+M: </strong>Expend or collapse a selected code fragment. The code collapsed depends on where the cursor is located<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber8.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="83" alt="vsShortcutsNumber8" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber8_thumb.jpg" width="244" border="0"></a> <br> <li><strong><strong>Ctrl</strong>-B + <strong>Ctrl</strong>-T: </strong>Toggle code bookmark<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber9.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="83" alt="vsShortcutsNumber9" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber9_thumb.jpg" width="244" border="0"></a> <br> <li><strong><strong>Ctrl</strong>-Alt-P:</strong> Attach the debugger to a process. This is insanely useful for debugging ASP.NET web sites without having to start the project in debug mode<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber10.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="181" alt="vsShortcutsNumber10" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber10_thumb.jpg" width="244" border="0"></a> <br> <li><strong><strong>Ctrl</strong>-Alt-L: </strong>Show the solution explorer<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber11.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="244" alt="vsShortcutsNumber11" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber11_thumb.jpg" width="161" border="0"></a>&nbsp;<br> <li><strong><strong>Ctrl</strong>-Shift-A / Alt-Shift-A:</strong> Add a new item to your project / add an existing item to your project<br><br><a href="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber13.jpg"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="156" alt="vsShortcutsNumber13" src="http://tech-cats.net/blog/images/EssentialVisualStudioShortcuts_B69E/vsShortcutsNumber13_thumb.jpg" width="244" border="0"></a> <br></li></ol> <p>The above list is my top 13. Did I miss any essential ones? What are yours?</p> <h3><u>Bonus:</u></h3> <p>The following work in Visual Studio just like they do in most other Windows application.</p> <p><strong><strong><strong>Ctrl</strong></strong>-N:</strong> Add a new file</p> <p><strong><strong><strong>Ctrl</strong></strong>-S:</strong> Save file</p> <p><strong><strong><strong>Ctrl</strong></strong>-Z / <strong><strong>Ctrl</strong></strong>-Y:</strong> Undo typing / Redo typing</p> <p><strong><strong><strong>Ctrl</strong></strong>-F:</strong> Bring up the "Find" dialog</p> <p><strong><strong><strong>Ctrl</strong></strong>-H:</strong> Bring up the "Replace" dialog</p> <p><strong><strong><strong>Ctrl</strong></strong>-Tab: </strong>Scroll forward through open windows</p> <p><strong><strong><strong>Ctrl</strong></strong>-Shift-Tab:</strong> Scroll backwards through open windows</p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com9tag:blogger.com,1999:blog-7367794905551891931.post-65985603431204902442008-03-03T11:06:00.001-05:002008-03-03T11:10:53.593-05:00Creating a Gmail Like Ajax Status Display<p>Most of us geeks know and love Gmail. It has a very nice interface and it is an inspiration to constantly improve our own web applications. Today, I set out to create an unobtrusive Gmail like page status message, much like the one shown in the screen shot:</p> <p>&nbsp;<img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="105" alt="gmail_screnshot" src="http://boyan.tech-cats.com/images/blog/CreatingaGmailLikeAjaxStatus_B90D/gmail_screnshot.jpg" width="644" border="0"> </p> <p>My requirements were simple:</p> <ol> <li>Should look like the one used in Gmail <li>Should be able to just include the source JavaScript file without any further configuration <li>Should allow the user to specify a custom status message to be displayed <li>Should allow for the use of custom styles but none are required <li>Integrates with Prototype.js Ajax requests</li></ol> <p>The only prerequisite is Prototype.js v1.6</p> <p>Let's get started. If all you care about is the end result. Here how to include it in your page:</p><pre class="js" name="code">&lt;script type="text/javascript" src="prototype.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="ajaxStatusDisplay.js"&gt;&lt;/script&gt;
</pre>
<p><br>That's it! Now whenever you make an Ajax request with Prototype, you will see a message popup with the text "Loading...". You can check out the end results at <a href="http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Simple.html" target="_blank">http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Simple.html</a></p>
<p>Now to the advanced features.</p>
<p>As stated #3 and #4 above, two of our requirements are to allow the user to specify a custom status message and custom CSS styles for the displayed message. Satisfying the first one of those requirements is done by having the user create an element on the page with the custom message like so:</p><pre class="xml" name="code">&lt;div id="ajaxStatusDisplay_userMessage"&gt;
Loading...Wait a Minute!
&lt;/div&gt;
</pre>
<p><br>This element does not have to be a div. It could be a any text container such as span or even input (text or hidden). What is important is the id of the element, it has to be set to "ajaxStatusDisplay_userMessage" for the custom message to be picked up.</p>
<p>The next requirement works much in the same way. Custom styles can be specified by creating two CSS classes with certain names: "ajaxStatusDisplay_userStyle" and "ajaxStatusDisplay_userMessageStyle". Here an example that will produce the default look:</p><pre class="css" name="code">&lt;style type="text/css"&gt;
.ajaxStatusDisplay_userStyle {position:absolute;left:45%;top:2px;height:10px;}
.ajaxStatusDisplay_userMessageStyle {
background:#FFF1A8 none repeat scroll 0%;color:#000;padding: 0pt 5px;
font-family:Arial, Helvetica, sans-serif;font-size:14px;font-weight: bold;
text-align:center;width:100%
}
&lt;/style&gt;
</pre>
<p>And here is another example that will produce a white text on black background in the top right corner:<br>&nbsp;</p><pre class="css" name="code">&lt;style type="text/css"&gt;
.ajaxStatusDisplay_userStyle {position:absolute;left:94%;top:0px;height:10px;}
.ajaxStatusDisplay_userMessageStyle {
background-color:#000;
color:#fff;
font-family:Arial, Helvetica, sans-serif;
padding:2px;
width:100%;
}
&lt;/style&gt;
</pre>
<p><strong>To use the your own CSS styles,</strong> you <strong>do not need to do anything</strong> but define them as shown above (as compared to the initial version where you needed to edit the file "ajaxStatusDisplay.js" and set the "useUserCssStyles" variable to "true").</p>
<p>A small extra feature is the ability to change the status message at runtime. This is done by using (you guessed it), the "setStatusMessage" function as follows:</p><pre class="js" name="code">// Set the status message based on the value in the "userMessage" input field
ajaxStatusDisplay.setStatusMessage($F('userMessage'));
</pre>
<p><br>To see setting the status message in action along with using a custom CSS style, check out <a href="http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Advanced.html" target="_blank">http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Advanced.html</a></p>
<p>You can view all the sources at:</p>
<p>JavaScript Source: <a href="http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay.txt" target="_blank">ajaxStatusDisplay.txt</a><br>Simple Example Source: <a href="http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Simple.txt" target="_blank">ajaxStatusDisplay-Simple.txt</a><br>Advanced Example Source: <a href="http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Advanced.txt" target="_blank">ajaxStatusDisplay-Advanced.txt</a></p>
<p>You can download the JavaScript source at:</p>
<p><a href="http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay.js" target="_blank">http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay.js</a></p> Ben Richardshttps://plus.google.com/106560991906301776392noreply@blogger.com2