Jekyll2023-08-11T00:53:47+00:00https://urda.com/feed.xmlWritings of UrdaYour friendly West Coast Software EngineerPeter UrdaUrda on Mastodon2023-08-10T12:00:00+00:002023-08-10T12:00:00+00:00https://urda.com/blog/2023/08/10/fediverse-mastodon<p>I am happy to announce I have launched my own Mastodon at <a href="https://urda.social/@urda">urda.social/@urda</a> and added an icon to the bottom of my website. You can follow me at <strong><code class="language-plaintext highlighter-rouge">@urda@urda.social</code></strong> from any Mastodon server.</p>Peter UrdaI am happy to announce I have launched my own Mastodon at urda.social/@urda and added an icon to the bottom of my website. You can follow me at @urda@urda.social from any Mastodon server.Urda’s Projects and Programming2023-05-30T08:00:00+00:002023-05-30T08:00:00+00:00https://urda.com/blog/2023/05/30/projects-programming<p>Are you wondering what I have been up to? You should visit <a href="https://urda.com/projects/">urda.com/projects</a> and take a look.</p>Peter UrdaAre you wondering what I have been up to? You should visit urda.com/projects and take a look.Urda’s PGP Key for Amazon2017-05-08T08:00:00+00:002017-05-08T08:00:00+00:00https://urda.com/blog/2017/05/08/amazon-key<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code>-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
I recently joined Amazon! I wanted to take a moment and setup a PGP key for
my Amazon e-mail.
*** This message is signed by my personal and work keys! ***
My Amazon key is:
pub 4096R/0679B2CB 2017-05-05
Key fingerprint = 9B51 83FF D39B BEC9 F4ED F95B 1CF3 F60F 0679 B2CB
My personal key is:
pub 4096R/194E3161 2016-05-31
Key fingerprint = CA0B 9733 4F94 49EB 5AFF CB93 240B D54D 194E 3161
To fetch my new Amazon key from a public key server, you can simply run:
gpg --keyserver pgp.mit.edu --recv-key 0679B2CB
Regards,
~ Peter Urda
-----BEGIN PGP SIGNATURE-----
iQIcBAEBCgAGBQJZECS7AAoJECQL1U0ZTjFh9JgP/iAhE2vyD244hK4dr0ixV+6v
dnry6Vft9VKG6P3ztpGdmuuSo3rZedChWyE+1v7o4U3Fft4z73kp3DhTyHydrp3k
38QYBKNIdOq6z7KUVJg8yIJyU+Ratw+qi3mv0k9pSofyzkIHLnBTGZO62fiuI24x
lJnOZk5u42vpGISBR+tiB9BR5SUogLMN+H4Thk7m6EDlkcfkFCqqIOTHduQo6E7E
QEzwgdFScfA4TOBTa1BsXRop+6jZ/64jyelTMiJV65lIG6GEsi0b2lwlBzS9LEdK
SefX47iIKq7JsYLl7lbVSIp5QyOdhFxA9UVoLWpCaoAHHevx1QLG0hE3515Efa/2
GdG6XMg1Gk1oCxQOso4Twl17KvN3czR64vpIAeye3X3CwdmTgo9/m/aWQT4oFjEC
aMmj+osbJ7BAIYmESfiRey7NO934Qi8WtaNFO4MipEiBPAHmxU3l8VIWnWsEThQu
063It5YV8hADU06KMV7LsaKHZTeK2CL+QpdMHWZplmYWsnG4u8MbYUQLhTnWQFsU
t5DNiPNi7gpPNvrPq881pGjO2fbxrokZXzfstxGpP6EnQ86R5lZ08WndO8mSqYLC
DFxsph4cm8hu5ndCkplBnT9AQK1N5B0YMCT6QdIfOu34GNRNfW7/aM0t53Z/mEim
3cCMxZzHK8zPUZK/b8MMiQIcBAEBCgAGBQJZECS7AAoJEBzz9g8GebLL9JgP/09e
SIFPBS/1mxNC9jxO9PzHfoDFGturq3G9RbZIc1nGYAsj1YGhsMs0ACGtGWWPCL0v
nz1iDyt7AR3Y6lFyKd/cfwKtpHPFyYtTRy82az4bgM5Dbf4iM9mBq/12jCkGbHu+
Bu8IGu/c8SgPbHZ0/NwvebvOftMUuAsx4Zasar0mPpAbp3Qehu438Jm/zZedDKg1
MA8lFf0d6Qbtt3g5KqtEUvRhWDMT6DnpbWrRfIBMh1AA1GRiFD06SmB5Uz0OACRA
L4tacWNxKHWHkN0Q0Z6HWx+TIBceGQO0p5t/zRzQX2HBW5rqVgwvwlzSiotKxdXx
+EGSa9q9BJdR1jNhx3iHNv+Lvf9YL24Cpx8Cg4WU2JS4fC+QTErpknDtCqIuaBl0
kgUI9CZ81f1kEuC2nWRSe63mEXxd4ffVHs8KrIIPYXXGRUwsRbTjc69/+oWZj92n
krcT0XmtVyI6ZsjYy7WHzSdGdVQPtRZ6SuAg2ZFrm2oFmZOkePXIJWr/G9k0nHpj
JjDUDFdO7rdt28awdWrl85dHZM2DXSEesolknnmGH6X3YKBvpIsyPEr4h74o9k3h
thONF9FCBskC17VBdlkHx98UC4AX91OprFyMm4pRADNyyX20ORkFh7Px3XIvsIOU
38y0SsWLqZCwIpC4/KWB+VcO1Q+YDfhera466VuL
=/nWG
-----END PGP SIGNATURE-----
</code></pre></div></div>Peter UrdaI joined Amazon and now have a key for my e-mail there!Urda’s PGP Key Transition Statement2016-05-31T20:47:07+00:002016-05-31T20:47:07+00:00https://urda.com/blog/2016/05/31/key-transition<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code>-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
For a number of reasons, I have recently set up a new OpenPGP key and
will be transitioning away from my old one. All correspondence should use
the new key now.
*** This message is signed by both keys to certify the transition. ***
The old key was:
pub 2048R/9CED75CD 2012-07-08
Key fingerprint = AF7C 0F76 C05C 7B84 9BBF E400 FDBB EE4F 9CED 75CD
The new key is:
pub 4096R/194E3161 2016-05-31
Key fingerprint = CA0B 9733 4F94 49EB 5AFF CB93 240B D54D 194E 3161
To fetch my new key from a public key server, you can simply run:
gpg --keyserver pgp.mit.edu --recv-key 194E3161
If you already know my old key, you can now verify that the new key is
signed by the old one:
gpg --check-sigs 194E3161
If you don't already know my old key, or you just want to be double
extra paranoid, you can check the fingerprint against the one above:
gpg --fingerprint 194E3161
Regards,
~ Peter Urda
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQEcBAEBCgAGBQJXTfJsAAoJEP277k+c7XXNWoMH/1QDBt8zhfFCWYKGzFByoH/w
574DASk204CigkeD4nShfUY/Jb0qL1fw+wa3nt4sajbwFQjr4wbZ3aa89WncYIRX
ziLpmBTz+8YyUAXMV7+RPZ/+28Hwbh5HgxtlRHF19Qjy+aW5VVyAsqsonbquOsjb
z9onaOO98+nr8+hbeXEvHb1E+sQMagJszyVEzsZadHVzNu/3WNqWdkA6DZ57EEc6
3oW2e++xhN005Ho/L7HLlK71csfqSapOQLER7dmpSWFjEPsfxqtLG4V1Xnwlc+Ey
vb23ttRaa5rGRNiQONS/fY5mgE+aVy6addWjGOc3gXphih8G19MkixrHvNkcSmiJ
AhwEAQEKAAYFAldN8mwACgkQJAvVTRlOMWFagw/9FoXBM5tvw6WUU2kv/6xWsQbg
UaDYFiEdfi03G88crMo8KQrekapPyINiJYFIGKZFd6r5dSVrGgTnrQqvgmIExdT3
jE3dHUzJlALYD5hecZyd+7M1VcrW5xnFlafyLW0bHCDBVtFUz7E6LNvSNATxo7Pc
Bwj4iYCEOL670rJs5kbzHJeogFho/yX6k/0Aj5dOBwsGiXi0LCNlaxLwl4BwCMQQ
lPNyIDKCj0jFXqA2ZE/6s/Q/UmpdqNUPN8dzuL1QheXTEl4+e30v9q4X5QZaD9cH
s3uZCZxBsWIQdTGnfc8ZerGUN0QxeURquFrxEBIqh9nOlZ8BjbBbVXKj6Cxgj2hd
2LxT/uj8Yd5Wa9ikqv6pmCjqWFE8Dx+bQz7YeSvNk0QAB/EG4qhNE+Ppkvqe1zga
PHX3uU6XXGx/OrFxkCDtvRxJ7/j0WCNFX1N/IpqYHba1DS+Fk5J0Mz7mS/tB9Snn
YFwPkqimXBT6CkUZ+ZKyV2AJyajPImFT0Th4G5zJx8MR3hjkl2cFdtWZ5M4utfqQ
ogjQrIJcHl9K74loUa5jYJzgbbNQciricDRdZlvqvbUM0ddEzzIBB8oF6rAbnyZz
qvPj8pE56nbMcB8RRTgPAjO79dKTzSaVjbQWgFHSehQ7RX1fdVYrefsd3WvyGlKY
q2sCuLYZF85m4IvJGbY=
=lJkD
-----END PGP SIGNATURE-----
</code></pre></div></div>Peter UrdaI changed OpenPGP keys. This is the public proof!Urda’s OpenPGP Key2016-05-31T20:00:00+00:002016-05-31T20:00:00+00:00https://urda.com/blog/2016/05/31/openpgp-key<p>Here is a copy of my OpenPGP key for verification and encryption purposes.
This key can be used for signature verification and encryption purposes.</p>
<h3 id="get-my-key-from-a-keyserver">Get my key from a keyserver:</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code>gpg <span class="nt">--keyserver</span> pgp.mit.edu <span class="nt">--recv-key</span> 194E3161
</code></pre></div></div>
<h3 id="my-keys-fingerprint">My key’s fingerprint:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code>pub 4096R/194E3161 2016-05-31
Key fingerprint = CA0B 9733 4F94 49EB 5AFF CB93 240B D54D 194E 3161
</code></pre></div></div>
<h3 id="full-public-key">Full public key:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code>-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1
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
=WYFT
-----END PGP PUBLIC KEY BLOCK-----
</code></pre></div></div>
<h3 id="transition-notice">Transition notice:</h3>
<p>If you used my older key before, you can review my
<a href="/blog/2016/05/31/key-transition">transition notice here</a>.</p>Peter UrdaUse this to send me secure communications or verify my signed messages.Writings Reborn2015-10-18T00:00:00+00:002015-10-18T00:00:00+00:00https://urda.com/blog/2015/10/18/writings-reborn<p>This blog originally started out as a simple place for posting my daily reports
when I worked at Mercer. Little did I know that a number of my articles would
appear across the internet in other blogs and even used in a few Stack Overflow
answers. So when it came time to migrate my website to a more modern platform
and to a different domain name I had a number of problems to handle.</p>
<p>My website ran on a technology stack that I found difficult to maintain over
time.</p>
<ul>
<li>Domain: peter-urda.com</li>
<li>Content Platform: <a href="https://wordpress.org/">WordPress</a></li>
<li>Analytics Engine: <a href="https://piwik.com/">Piwik</a>
<ul>
<li>Both required PHP</li>
<li>Both required MySQL Database</li>
</ul>
</li>
<li>Hosting Group: <a href="https://www.dreamhost.com/">DreamHost</a></li>
</ul>
<p>Getting started out these tools fit the bill. WordPress at the time was as
lightweight blog system, Piwik let me have my own private analytics, and
DreamHost allowed for a quick solution to get hosting online quickly. For the
duration of my time at Mercer, and a period following that the entire stack
allowed me to publish content online without having to worry about the details.</p>
<p>As life went on I found it more and more difficult to keep up with this
technology stack. WordPress grew heavier, Piwik had it’s required updates, and
all of which required keeping up with what version of PHP was needed. I spent
more time worrying about keeping the website online and secure instead of
generating content. Finally, I just honestly was not happy with my domain name.</p>
<p>This changed in January of 2015 when two events happened. One, I started a new
job and saw a lot of fantastic websites and related web services from my new
co-workers. Two, I was able to register my new domain name <strong>urda.cc</strong> which was
an ideal domain name for myself. I started planning my migrations shortly after.</p>
<p>I had a few “must haves” on building my new website:</p>
<ul>
<li>No more databases</li>
<li>No more self-hosted solutions</li>
<li>Management needs to be kept simple</li>
<li>Content is king, and should be easy to write</li>
<li>Easy to deal with my old links on WordPress</li>
</ul>
<p>I narrowed down my potential solutions to Squarespace and GitHub Pages. Both
allowed me to leave databases, provide the hosting for my website, and had great
author tools for generating content. Both of course had drawbacks though. GitHub
pages however did not provide analytics for my website. Squarespace did not have
a great solution to providing URL redirects for my new site and URL structure.
GitHub pages required some setup on a local machine, but Squarespace required
that I do all my content making on their platform.</p>
<p>After back and forth, and testing both Squarespace in a sandbox and setting up
a GitHub pages site, I settled with GitHub pages. I was really drawn by the fact
that I could just start making a post in familiar markdown and everything would
end up as static files to be served up on the internet. Using this I created a
new technology stack, and a migration plan.</p>
<ul>
<li>Domain: urda.cc (leaving peter-urda.com)</li>
<li>Content Platform: <a href="https://jekyllrb.com/">Jekyll</a></li>
<li>Analytics Engine: Google Analytics</li>
<li>Hosting Group: <a href="https://pages.github.com/">GitHub Pages</a></li>
<li>Simple NGINX instance to raise <code class="language-plaintext highlighter-rouge">301 Moved Permanently</code> from peter-urda.com
<ul>
<li>While I have to manage the NGINX, it is only for a short period during
the migration window</li>
</ul>
</li>
</ul>
<p>One of the major features of this stack was being able to redirect my old pages
to my new website. While yes I do have a NGINX instance handling the actual
peter-urda.com requests to urda.cc, I also wanted to use <code class="language-plaintext highlighter-rouge">/blog/</code> in front of
all my URLs for post. I was lucky to be able to use
<a href="https://github.com/jekyll/jekyll-redirect-from">jekyll-redirect-from</a>. This
allowed me to simply add a <code class="language-plaintext highlighter-rouge">redirect_from</code> in my posts to handle my old URLs.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code>redirect_from: /YYYY/MM/post-title/ # Old WordPress URL to redirect from
</code></pre></div></div>
<p>Any sites that used an old peter-urda.com link will first hit my
NGINX instance, which will then send them to urda.cc, which will then send them
to the new URL. Eventually I will just have my old domain pointed to the new one
but this allows search engines to update their links first.</p>
<p>It’s been a long road, but I am very happy that I can just open up a text
editor and start writing again. No complications, no noise, just content. Since
my Mercer days I have spent far more time using Python, Git, and OS X and I am
excited to start putting out posts regarding all the new technologies I have
started working with.</p>Peter UrdaAfter a long hiatus, Writings of Urda returns!Why I am migrating AWAY from Jungle Disk2012-08-26T00:00:00+00:002012-08-26T00:00:00+00:00https://urda.com/blog/2012/08/26/why-i-am-migrating-away-from-jungle-disk<p><strong>DISCLAIMER: I have been a Jungle Disk customer since 2008, and this is my
story of watching their fall from fame.</strong> I have used Jungle Disk for years, and
have been very satisfied with the pricing and the software up until recently. I
became a Lifetime member of the Desktop Edition when it was offered for sale as
soon as possible, and used it up until this week. However I have opted to leave
the service for many KEY reasons.</p>
<ul>
<li>The software has NOT been updated in months, I can’t even remember when a news
item was last posted for a release</li>
<li>The blog has not been updated in months</li>
<li>The development team has been NEAR silent. Back in 2011 I had opened a
ticket noting that I was concerned by the lack of updates from the team.
Support said they would request an update, but nothing came from that.</li>
<li><a href="https://www.daemonology.net/blog/2011-06-03-insecurity-in-the-jungle.html">There are some known security concerns with Jungle Disk at this time.</a></li>
<li>At one point the blog was infected by spammers, this now marks the blog as
phishing in OpenDNS and other popular prevention groups. OpenDNS prompts
with the following screen if you try to visit their blog:</li>
</ul>
<p><a href="/content/20120826/jdblog.png"><img src="/content/20120826/jdblog.png" alt="Jungle Disk Blog Spam" /></a></p>
<p>So basically because the <strong>development team has been very quiet with no news,
the blog has stopped updating, and no new software releases to be seen</strong> all had
me very concerned about my backup solution. Obviously, you want your cloud
provider for backups and data to be actively and communicating with its users,
not appearing to be dead in the water.</p>
<p>So instead of waiting for Jungle Disk to be exploited, or run the risk of having
to migrate to a new backup system WHILE recovering from a possible Jungle Disk
disaster, it was critical to find a new solution. I started researching where to
go next. I found my solution and started my migration <strong>to Dropbox.</strong></p>
<p>Dropbox is actively developed, has secure standards in place, is very fast, is
available on all major platforms (Windows, Linux, Mac), and is very slick. The
dev team communicates with users very often, and the software is kept up to
date. Thanks to fortunate timing, the Price of Dropbox recently moved down to
100 bucks a year for 100GB, or about 8.34 dollars a month. I was paying about
this already with Jungle Disk and using under 100GB, so the move was clear.</p>
<p>Migration was a snap, I simply moved everything from local storage into Dropbox,
and symlinked things as necessary (such as my iTunes library). This in turn
removed the need for the Jungle Disk scheduled backups because Dropbox handles
syncing changes right away and seamlessly too. On top of this, Dropbox auto
revisions files for up to 30 days, or if you pay for a feature to keep all
revisions forever. Overall, it only took about 2 to 3 days to migrate away from
Jungle Disk to Dropbox, and remove all files from my Jungle Disk.</p>
<p><strong>Dear Jungle Disk:</strong> I enjoyed your product since 2008, and even used it twice
to safely recover from system and hardware disasters. However, your lack of
communication, updates, and potentially a major security threat has forced me to
move to another solution. Perhaps the day will come where things get
straightened up and the development team shows backup, thus making my Lifetime
subscription worth it again. Perhaps our ways will cross again, and I do hope
the development team gets it together and we see Jungle Disk become a true
competitor again.</p>Peter UrdaJungle Disk stagnation, woes, and why I am leaving the product.Using PowerShell to Clean Up an Xml File2012-07-12T00:00:00+00:002012-07-12T00:00:00+00:00https://urda.com/blog/2012/07/12/ps-clean-up-an-xml-file<p>Sometimes you are given an Xml file, either handmade or machine generated, to
work with. Many times, this Xml file can be a mess, with indentation all wrong
or every single node on a single line or two. Within PowerShell, I have created
a simple function which accepts just a single input of the file path, and
attempts to locate the Xml file and “prettify” for easier reading. This function
is only 19 lines long (for a total of 31 when including the documentation) and
is a great tool to add to your PowerShell profile!</p>
<p>There is error handling at each step, to verify that there was input, the file
exists, can be accessed, and the save operation completed. Just simply provide
the function the path to the target Xml file, and fire away!</p>
<p>Here is the function you can load into your session, or add to your profile for
future use:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Format-Xml</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm"><#
</span><span class="cs">.SYNOPSIS</span><span class="cm">
Loads, formats (prettifies), and resaves an Xml file for easier reading and editing.
</span><span class="cs">.DESCRIPTION</span><span class="cm">
This function attempts to load and re-save an xml file. When the Xml file is resaved,
the document is saved in a "prettified" or formatted view. This is useful for when
you have an Xml file, but all the formatting is messed up, or the whitespace is not uniform.
</span><span class="cs">.NOTES</span><span class="cm">
Author : Peter Urda (@Urda)
</span><span class="cs">.LINK</span><span class="cm">
Script source: http://urda.cc/blog/2012/07/12/ps-clean-up-an-xml-file/
#></span><span class="w">
</span><span class="kr">param</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$FilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$FilePath</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$FilePath</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nv">$FileObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nv">$FilePath</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$FileObject</span><span class="o">.</span><span class="nf">IsReadOnly</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nv">$xml</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">Xml</span><span class="w">
</span><span class="nv">$xml</span><span class="o">.</span><span class="nf">Load</span><span class="p">(</span><span class="nv">$FileObject</span><span class="o">.</span><span class="nf">ToString</span><span class="p">())</span><span class="w">
</span><span class="nv">$xml</span><span class="o">.</span><span class="nf">Save</span><span class="p">(</span><span class="nv">$FileObject</span><span class="o">.</span><span class="nf">ToString</span><span class="p">())</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"/!\ An error occured /!\"</span><span class="w"> </span><span class="nt">-Foreground</span><span class="w"> </span><span class="nx">Black</span><span class="w"> </span><span class="nt">-Background</span><span class="w"> </span><span class="nx">Red</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Exception</span><span class="o">.</span><span class="nf">Message</span><span class="w"> </span><span class="nt">-Foreground</span><span class="w"> </span><span class="nx">Black</span><span class="w"> </span><span class="nt">-Background</span><span class="w"> </span><span class="nx">Red</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"/!\ File is read only /!\"</span><span class="w"> </span><span class="nt">-Foreground</span><span class="w"> </span><span class="nx">Black</span><span class="w"> </span><span class="nt">-Background</span><span class="w"> </span><span class="nx">Red</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"/!\ File not found /!\"</span><span class="w"> </span><span class="nt">-Foreground</span><span class="w"> </span><span class="nx">Black</span><span class="w"> </span><span class="nt">-Background</span><span class="w"> </span><span class="nx">Red</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Usage: Format-Xml \Path\To\Xml\File"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>Peter UrdaPut PowerShell to work at cleaning up your XML files.Using PowerShell to Delete Files Not Written to in X Days2011-09-02T00:00:00+00:002011-09-02T00:00:00+00:00https://urda.com/blog/2011/09/02/using-powershell-to-delete-files-not-written-to-in-x-days<p>If you have ever had any type of program or application that keeps logs or fouls
up the <strong>temp</strong> directory in your system you know the frustration that comes
from wasted disk space with old, unused files. Luckily, you can create a very
basic PowerShell script to check a given folder and delete files that are older
than a set number of days.</p>
<p>Let’s start with a basic script for this. We will have two variables:
<strong>FilePath</strong> and <strong>FileDays</strong>. The file path is used for specifying what folder
we want PowerShell to search against. If a file’s last write timestamp is less
than the file days variable the script will delete said file from the directory.
For the sake of simplicity, this script does not check dates on subdirectories
nor does it attempt to delete subdirectories. The PowerShell script is just
interested in files directly in the <strong>FilePath</strong>.</p>
<p>So enough talk, here is the script:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="c"># Path to purge old files from</span><span class="w">
</span><span class="nv">$FilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\temp\"</span><span class="w">
</span><span class="c"># Define the minimum number of days since last write time</span><span class="w">
</span><span class="c"># for a file to be purged. Set this to a negative number, since</span><span class="w">
</span><span class="c"># it is in the past.</span><span class="w">
</span><span class="nv">$FileDays</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"-30"</span><span class="w">
</span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$File</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nv">$FilePath</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where</span><span class="w"> </span><span class="p">{</span><span class="o">!</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PsIsContainer</span><span class="p">)})</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="kr">if</span><span class="p">(</span><span class="nv">$File</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddDays</span><span class="p">(</span><span class="nv">$FileDays</span><span class="p">))</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="n">del</span><span class="w"> </span><span class="nv">$File</span><span class="o">.</span><span class="nf">FullName</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Like I said, really simple. You could schedule this to run as a task, or you
could even re-write this script to act as a function within another PowerShell
script. This script is well suited for purging files from a log folder, say
<strong>C:\dev\logs</strong> if the log files are created containing the current date in the
file name (something-Year-Month-Day.log). This way, you will only have a month
of log files at any given time. Once this script is set up, it is pretty much a
“set it and forget it”.</p>Peter UrdaUse PowerShell to clean up those old files that have not been written to or altered in a while.Build Objects With Interfaces2010-11-23T00:00:00+00:002010-11-23T00:00:00+00:00https://urda.com/blog/2010/11/23/build-objects-with-interfaces<p>Being able to drop certain chunks of code from one piece of software to another
piece of unrelated software is a powerful thing. Not only does this save time,
but it will also allow you to build a small library of tools you find useful in
all your applications. Interfaces in C# is just one of many ways to build more
modular objects that have similar behavior.</p>
<p>We are going to create an interface <strong>IHuman</strong> to build people objects from.
Everyone can agree that all people have a First Name, Last Name, Age, and have
some ability to speak. So we will wrap that up into a neat interface.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IHuman</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">fname</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">string</span> <span class="n">lname</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">int</span> <span class="n">age</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">void</span> <span class="nf">Speak</span><span class="p">(</span><span class="kt">string</span> <span class="n">input</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>An interface is a very basic chunk of code in C#. It is like a checklist of
objects and actions that all derived classes should have. The way each class
handles each dimension of the <strong>IHuman</strong> will be unique to each class. So now we
will create just a basic person object from the interface. I will then use
Visual Studio to implement the interface, and this will be the result:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">IHuman</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">fname</span>
<span class="p">{</span>
<span class="k">get</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">set</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">lname</span>
<span class="p">{</span>
<span class="k">get</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">set</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">age</span>
<span class="p">{</span>
<span class="k">get</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">set</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Speak</span><span class="p">(</span><span class="kt">string</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NotImplementedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As you can tell, you’ll need to go through each method and implement it for use.
I simply took a moment to do some clean up, and added logic to my <strong>Speak</strong>
method:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">IHuman</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">fname</span>
<span class="p">{</span>
<span class="k">get</span><span class="p">;</span>
<span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">lname</span>
<span class="p">{</span>
<span class="k">get</span><span class="p">;</span>
<span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">age</span>
<span class="p">{</span>
<span class="k">get</span><span class="p">;</span>
<span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Speak</span><span class="p">(</span><span class="kt">string</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now I can build a simple program and declare a Person Object. Then I can set
variables within the object and/or use any of the methods associated with it.
Yet, I need another object to describe a programmer. Again, I’ll use the
<strong>IHuman</strong> interface and make the needed changes to my methods. I’m also adding
a custom method in this class as another way to speak.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Programmer</span> <span class="p">:</span> <span class="n">IHuman</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">fname</span>
<span class="p">{</span>
<span class="k">get</span><span class="p">;</span>
<span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">lname</span>
<span class="p">{</span>
<span class="k">get</span><span class="p">;</span>
<span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">age</span>
<span class="p">{</span>
<span class="k">get</span><span class="p">;</span>
<span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Speak</span><span class="p">(</span><span class="kt">string</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">result</span> <span class="p">=</span> <span class="s">""</span><span class="p">;</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">string</span> <span class="n">s</span> <span class="k">in</span> <span class="n">input</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">c</span> <span class="p">=></span> <span class="n">Convert</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="m">2</span><span class="p">)))</span>
<span class="p">{</span>
<span class="n">result</span> <span class="p">+=</span> <span class="n">s</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">DudeInPlainEnglish</span><span class="p">(</span><span class="kt">string</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Sorry my bad... "</span> <span class="p">+</span> <span class="n">input</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you pull the interface and two objects together, you can build a simple
console application to prove this proof of concept:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="rougecssclass"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Person</span> <span class="n">MyPerson</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">();</span>
<span class="n">MyPerson</span><span class="p">.</span><span class="n">fname</span> <span class="p">=</span> <span class="s">"John"</span><span class="p">;</span>
<span class="n">MyPerson</span><span class="p">.</span><span class="n">lname</span> <span class="p">=</span> <span class="s">"Smith"</span><span class="p">;</span>
<span class="n">MyPerson</span><span class="p">.</span><span class="n">age</span> <span class="p">=</span> <span class="m">25</span><span class="p">;</span>
<span class="n">MyPerson</span><span class="p">.</span><span class="nf">Speak</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Hello I am {0} {1}!"</span><span class="p">,</span>
<span class="n">MyPerson</span><span class="p">.</span><span class="n">fname</span><span class="p">,</span>
<span class="n">MyPerson</span><span class="p">.</span><span class="n">lname</span><span class="p">));</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">();</span>
<span class="n">Programmer</span> <span class="n">Me</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Programmer</span><span class="p">();</span>
<span class="n">Me</span><span class="p">.</span><span class="n">fname</span> <span class="p">=</span> <span class="s">"Peter"</span><span class="p">;</span>
<span class="n">Me</span><span class="p">.</span><span class="n">lname</span> <span class="p">=</span> <span class="s">"Urda"</span><span class="p">;</span>
<span class="n">Me</span><span class="p">.</span><span class="n">age</span> <span class="p">=</span> <span class="m">21</span><span class="p">;</span>
<span class="kt">string</span> <span class="n">UrdaText</span>
<span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Hey, I'm {0} {1}"</span><span class="p">,</span> <span class="n">Me</span><span class="p">.</span><span class="n">fname</span><span class="p">,</span> <span class="n">Me</span><span class="p">.</span><span class="n">lname</span><span class="p">);</span>
<span class="n">Me</span><span class="p">.</span><span class="nf">Speak</span><span class="p">(</span><span class="n">UrdaText</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">();</span>
<span class="n">Me</span><span class="p">.</span><span class="nf">DudeInPlainEnglish</span><span class="p">(</span><span class="n">UrdaText</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Running the program will produce this output:</p>
<p><a href="/content/20101123/humans.png"><img src="/content/20101123/humans.png" alt="Humans of C#" /></a></p>
<p>As you can tell the Programmer spits out binary when asked to speak, and it is
only when you call the <em>DudeInPlainEnglish</em> method against it is when you get a
readable format. The method also appends “Sorry my bad…” to the start of the
print out.</p>
<p>If we only had access to the interface, we would know what properties and
methods that each class must have when using said interface. Think of this
interface as a type of contract, where each class that uses it must (in some
fashion) use the properties and methods laid out. You can also think of an
interface as a very basic framework for all involved classes.</p>
<p>So the next time you are working on a bunch of objects that are closely related
to each other, consider using an interface.</p>Peter UrdaBeing able to drop certain chunks of code from one piece of software to another piece of unrelated software is a powerful thing. Not only does this save time, but it will also allow you to build a small library of tools you find useful in all your applications. Interfaces in C# is just one of many ways to build more modular objects that have similar behavior.