tag:blogger.com,1999:blog-146965922024-03-16T14:59:20.884-05:00Westing PeacefullyKent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.comBlogger846125tag:blogger.com,1999:blog-14696592.post-26377568319775469342024-03-16T14:58:00.006-05:002024-03-16T14:58:41.581-05:00The Meaning of "a cappella"<p> <span data-offset-key="f720c-0-0"><span data-text="true">Most of us (including me) have been educated to believe that "</span></span><span data-offset-key="f720c-0-1" style="font-style: italic;"><span data-text="true">a capella</span></span><span data-offset-key="f720c-0-2"><span data-text="true">" means "singing without instrumental accompaniment". But </span></span><span class="xv78j7m" data-offset-key="f720c-1-0" spellcheck="false" start="129"><span data-offset-key="f720c-1-0"><span data-text="true">Michael G. Knox</span></span></span><span data-offset-key="f720c-2-0"><span data-text="true"> </span></span><span><span data-offset-key="f720c-3-0"><span data-text="true">recently</span></span></span><span data-offset-key="f720c-4-0"><span data-text="true"> educated me further. He writes (with minor edits):</span></span></p><div data-contents="true"><blockquote class="xckqwgs x26u7qi x7g060r xi81zsa x6prxxf x1sy10c2 x11i5rnm xieb3on x1mh8g0r x1pi30zi x1swvt13" data-block="true" data-editor="ff32o" data-offset-key="btibt-0-0"><div class="_1mf _1mj" data-offset-key="btibt-0-0"><span data-offset-key="btibt-0-0"><span data-text="true">I’m glad you know what “a cappella” means -- </span></span><span data-offset-key="btibt-0-1" style="font-style: italic;"><span data-text="true">in the modern context</span></span><span data-offset-key="btibt-0-2"><span data-text="true">. But that wasn't originally the case.</span></span></div></blockquote><blockquote class="xckqwgs x26u7qi x7g060r xi81zsa x6prxxf x1sy10c2 x11i5rnm xieb3on x1mh8g0r x1pi30zi x1swvt13" data-block="true" data-editor="ff32o" data-offset-key="5jv2e-0-0"><div class="_1mf _1mj" data-offset-key="5jv2e-0-0"><span data-offset-key="5jv2e-0-0"><span data-text="true">The very reason that term was invented was to differentiate one style of orchestration from another. It did not originally mean “singing without instruments.”</span></span></div></blockquote><blockquote class="xckqwgs x26u7qi x7g060r xi81zsa x6prxxf x1sy10c2 x11i5rnm xieb3on x1mh8g0r x1pi30zi x1swvt13" data-block="true" data-editor="ff32o" data-offset-key="ag80e-0-0"><div class="_1mf _1mj" data-offset-key="ag80e-0-0"><span data-offset-key="ag80e-0-0"><span data-text="true">One of the earliest uses of the earlier version “alla capella” (Italian, meaning “according to the chapel”) was in 1742 by the German composer Johann Galliard. He used the term in a secular composition to denote that the style of the song was to mimic “chapel” music of the day. Since, in his experience, the chapel music of the mid-1700’s (Catholic) would have been instrumental, he was referring to a style of accompanied singing. He intended for the music to play in unison with the singers instead of being complementary of the singers. Over time, the term “alla capella” took on the Latin spelling “a cappella” in 1868 when it denoted "that instruments are to play in unison with the voices, or that one part is to be played by a number instruments." ["Chambers's Encyclopaedia," 1868]. In 1875, "a cappella" referenced, for one of the first times, non-instrumental music. But, as other chorales, sung as part of the church service, were written in the same and simple style, the expression "a capella" came in time to be applied to them also, despite their being sung without any instrumental accompaniment whatever. [The Music World, Sept. 11, 1875]</span></span></div></blockquote><blockquote class="xckqwgs x26u7qi x7g060r xi81zsa x6prxxf x1sy10c2 x11i5rnm xieb3on x1mh8g0r x1pi30zi x1swvt13" data-block="true" data-editor="ff32o" data-offset-key="ehiah-0-0"><div class="_1mf _1mj" data-offset-key="ehiah-0-0"><span data-offset-key="ehiah-0-0"><span data-text="true">In time, "a cappella" came to mean singing without instruments. How did this happen? It seems this linguistic shift came about because of the rise of Protestantism in the 1800’s. Most Protestant churches did not use instruments with their singing. Several prominent leaders, including John Calvin vehemently opposed instruments in worship on the grounds it was “too strongly tied to antiquated and unorthodox methods from before the Reformation.” So, in the late 1800’s and early 1900’s the normal “chapel” style was only singing and no instruments. Therefore, when one thought of music in the chapel style ("a cappella"), they thought of solely vocal music.</span></span></div></blockquote></div>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-46743309078827609742024-01-23T10:28:00.002-06:002024-01-23T10:28:19.406-06:00Satan Was Not Always Our Enemy<p>Because the serpent tempted Eve, God said that he would put hostility between the serpent's offspring and humanity.</p><p></p><blockquote>WEB Gen 3:4 Yahweh God said to the serpent, “Because you have done this, you are cursed above all livestock, and above every animal of the field. You shall go on your belly and you shall eat dust all the days of your life. 15 I will put hostility between you and the woman, and between your offspring and her offspring. He will bruise your head, and you will bruise his heel.”</blockquote><p></p><p>It seems then, that until that moment, there was no such hostility between us and Satan.</p>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com1tag:blogger.com,1999:blog-14696592.post-45600394957786231192023-12-18T14:54:00.003-06:002023-12-18T14:54:31.585-06:00Rust - Read a Single Keypress from the Keyboard, ala 'getch'<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="generator" content="LibreOffice 7.0.4.2 (Linux)"/>
<meta name="created" content="2021-03-08T08:24:41.456757055"/>
<meta name="changed" content="2021-03-08T17:12:10.345505531"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rust - Read a Single Keypress from the Keyboard, ala 'getch'</title>
<!-- The style.css file allows you to change the look of your web pages.
If you include the next line in all your web pages, they will all share the same look.
This makes it easier to make new pages for your site. -->
<link href="style.css" rel="stylesheet" type="text/css" media="all">
</head>
<body>
<h1>How to Read a Keypress from the Keyboard in Rust, similar to getch()</h1>
<h4>Kent West - kent.west@{that mail that swore to do no evil}</h4>
<p>I struggled for days to figure out how to get a simple keypress from the keyboard. Finally! I found a crate on <a href="https://crates.io">crates.io</a> that makes it as simple as using "getch()" from the ncurses library.</p>
<p>The reason I did not use <em>ncurses</em> is because it requires an initialization step, which swaps out the screen for a new blank screen, which then disappears when you're finished with the curses mode.</p>
<p>I would have used the <em>termion</em> crate, but search high and low on the 'Net for a <em>simple</em> snippet of code that shows just the absolute bare minimum for getting a keypress, and you'll get a headache, and no solution, unless you're smarter than I am (granted, that's not a very high bar, but still...) I do <em>not</em> understand why coders will not give the simplest, barest-possible, <em><strong>COMMENTED!!!!</strong></em> code snippets so the newbie can get started. (Yes, I'm angry about this; Rust could be such a beautiful platform, if non-life-long-coders could learn how to code in it from simple examples and good explanations.)</p>
<p>Here's how to get a keypress from the keyboard in Rust:</p>
<figure>
<figcaption>Configure 'Cargo.toml'</figcaption>
<pre>
$ cargo add getchar
</pre>
</figure>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
fn main() {
let keypress = getchar::getchar().unwrap(); // getchar() returns an "Option<char>" type that must be unwrapped to get to the 'char' goody inside.
println!("You pressed {}", keypress);
} // end of main()
</pre>
</figure>
</body>
</html>
Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-54246676120061525902023-11-11T13:42:00.002-06:002023-11-11T13:42:24.065-06:00Connect to Google Drive using "rclone" on Debian<ol style="text-align: left;"><li>Install "rclone"<br /><br /></li><ol><li><i># apt install rclone<br /><br /></i></li></ol><li>Use "rclone" to configure a "remote" for each connection. For example, if you have a work Google account and a personal Google account, you'd need a "remote" for each connection you want to use. Don't worry about making mistakes during this configuration; you can always delete the configuration and start over if need be. As the user with the Google Drive account:<br /><br /></li><ol><li><i>$ rclone --config</i></li><li>Assuming you don't already have a "remote" (you don't, or you wouldn't be following this set of instructions), choose <b>n</b> for "New remote".</li><li>The name for the new remote might be something like "GDrive" or "gdrive-work" or "my-personal-google-drive", etc.</li><li>"rclone" can connect to all sorts of services; it now gives you a list of services to select from. This list changes from time to time, so don't rely on remembering the number for next time you need to do this. At the time of this writing, the Google Drive item is number 18, so that's the one I'll select.</li><li>Setting the "Google Application Client Id" has some advantages, but it's a pretty complicated process for someone who has never done it before. For now, I'd keep it simple, and bypass this option by leaving this "client_id" option empty.</li><li>Ditto the "client_secret".</li><li>For "scope", you probably want the first option, "Full access [to] all files", because when you access Google Drive from your Debian workstation, you'll probably want full access to those files.<br /></li><li>The "service_account_file" should be left empty (unless you know what you're doing; you don't).</li><li>Do not "Edit advanced config".</li><li>Yes, do "Use auto config". This will open a web-browser window where you should log into your Google account, and then "Allow" rclone to access your Google account.</li><li>Assuming this is not a Shared drive (if it is, you'd know it), select "No".</li><li>Yes, keep this remote. This is essentially the "Save" step for the configuration of this remote.<br /><br /></li></ol><li>You've now configured your first Google Drive "remote". If you have a second Google account, you can configure it now by repeating the above process, starting at step 2, or you can "Quit config", and come back later to configure other remotes as the need arises. This configuration is saved in a plain-text file as "<i>~/.config/rclone/rclone.conf</i>". Instead of using the "rclone --config" "wizard" as we did above, you could've created this config file by hand, but who wants to do that error-prone process?<br /><br /></li><li>"<i>$ rclone listremotes</i>" will list the names of the remote[s] you have configured.<br /><br /></li><li>"<i>$ rclone ls gdrive-work:</i>" will list the files on the remote named "gdrive-work". Because this "ls" command traverses the file "tree", you may get a bigger listing than you want, so you may want to <i>ls</i> a smaller directory, like "<i>rclone ls gdrive-work:/todo/Week17</i>". Or if you wanted a tree view, "<i>rclone tree gdrive-work:</i>"<br /><br /></li><li>But most likely, you want the drive mounted. There are several ways to accomplish this.<br /><br /></li><ol><li>Using "rclone mount"<br /><br /><i>$ mkdir ~/GDrive4Work<br />$ rclone mount gdrive-work:/ ~/GDrive4Work<br /> </i><br />Note that you won't see anything happen, but you can open another terminal window (or file manager, etc) and browse to your mounted directory to access the files.<br /><br />Ctrl-C will unmount the drive.<br /><br />Alternatively you can background the process with an ampersand:<br /><i><br />$ rclone mount gdrive-work:/ ~/GDrive4Work &<br /></i><br />and when you want to unmount, <i>fg</i> to foreground the process, followed by Ctrl-C.<br /><br />Alternatively, you can mount it in daemon mode:<br /><br /><i>$ rclone mount gdrive-work:/ ~/GDrive4Work --daemon<br /><br /></i>and then unmount with:<br /><br /><i>$ fusermount -u ~/GDrive4Work<br /><br /></i>(you can also use <i>umount</i> instead of <i>fusermount -u</i>, but the documentation says this is unreliable)<br /><br />We probably want this mounting to take place at login of the Drive's owner.<br /><br />Make a little script and put it where you can access it. I have a <i>~/bin</i> directory for this purpose. Name the script something like "mount-GDrive.sh", and put the following into it:<br /><br /><div style="margin-left: 40px;"><span style="font-family: monospace;"><span style="background-color: white; color: black;">#!/bin/bash
</span></span><br /><span style="font-family: monospace;">
</span><br /><span style="font-family: monospace;"># Test if GDrive is mounted, and if not, mount it
</span><br /><span style="font-family: monospace;">mountpoint -q /home/[your user]/GDrive4Work || rclone mount gdrive-work:/ /home/[your user]/GDrive4Work --daemon</span><br /><span style="font-family: monospace;"></span></div><span style="font-family: monospace;">
<br /></span>Save the file, and make sure it's executable:<br /><br /><i>$ chmod +x ~/bin/mount-GDrive.sh<br /></i><br />Then place a call to this script at the bottom of your <i>~/.bash_profile</i> file:<br /><br /><i>~/bin/mount-GDrive.sh</i><br /><br />If you don't already have a <i>~/.bash_profile</i> you can create it, but make sure to source the <i>~/.bashrc</i> file by adding to the top of <i>~/.bash_profile</i> this line:<br /><br /><i>. ~/.bashrc</i><br /><br />(There's a space after the first dot.)<br /><br />Now, reboot your system and make sure it all works.<br /><br /></li><li> Using an <i>/etc/fstab</i> entry<br /><br />This method is probably not the best, because it will mount the drive at boot-time, regardless of the Drive's "owner" being logged in or not. Also, the mount will not be owned by the "owner", unless special care is taken in the <i>/etc/fstab</i> file. The following is here just for documentation purposes; you're probably better off using the first method above.<br /><br />You can add a line to <i>/etc/fstab</i> like the following:<br /><br /><i>gdrive-work:/ /home/[your user]/GDrive4Work rclone rw,noauto,nofail,_netdev,x-systemd.automount,args2env,vfs_cache_mode=writes,config=/home/[your user]/rclone/rclone.conf,cache_dir=/var/cache/rclone 0 0</i><br /><br />If you try to mount the drive now with:<br /><br /><i># mount /home/[your user]GDrive4Work</i><br /><br />you'll get a message that rclone is not a known type, and that fstab has been modified, but that you need to reload the systemd daemons. Nowadays <i>/etc/fstab</i> is really just a backwards-compatible configuration source for <i>systemd</i>. When you reboot, systemd will read this file and convert your added line to a systemd <i>unit</i>. Currently this line has not yet been added to systemd. You can verify this with the command:<br /><br /><i># systemctl list-units | grep -i "gdrive"</i><br /><br />Notice this command is run as "root" (you can also use "sudo" instead). This command should find no units containing the name "gdrive".<br /> <br />"systemctl" is kind of like the control command for systemd, and we're telling it to list the units that systemd knows about, piping ("|") the results through "grep" to display only those lines that contain the phrase "gdrive", regardless of capitalization ("-i"). If you leave off the <i>| grep -i "gdrive</i>", you'll get a list of all the units that systemd knows about.<br /> <br />These units are typically stored in <i>/usr/lib/systemd/system</i> (for system-installed units that usually should not be tinkered with) and <i>/etc/systemd/system</i> (for locally-configured units). You can also <i>ls</i> in those directories to verify there are no "gdrive"-related units there.<br /><br />But we don't have to wait for a reboot to let systemd know about the changes to fstab. We can just reload the daemons with:<br /><br /><i># systemctl --daemon-reload<br /></i><br />But that still doesn't work, because the old <i>mount</i> command doesn't recognize the "rclone" type (the systemd units still weren't created. But this is easy to fix: we just need to create a symlink, like so:<br /><br /><i># ln -s /usr/bin/rclone /sbin/mount.rclone</i><br /><br />Reload the systemd daemons again:<br /><br /><i># systemctl --daemon-reload<br /><br /></i>and now you can mount (as your normal user, not 'root') your "remote":<br /><br /><i>$ mount /home/[your user//GDrive4Work<br /><br /></i>You should now be able to access your Google Drive from the command-line or from any of your usual file managers, etc. Be aware though, that a reboot will mount the drive as "root", and your normal user probably will no longer have access to the Drive.<br /><br />While the drive is mounted, you can also see that systemd knows about this "unit":<br /><br /><i># systemctl list-units | grep -i "gdrive"</i><br /><br />... even though there still isn't a unit file in the two unit-file directories mentioned above.<br /><i><br />$ ls -R /etc/systemd/system | grep -i gdrive <br />$ ls -R /usr/lib/systemd/system | grep -i gdrive </i><br /><br />I was thinking this method created a unit-file automagically in <i>/lib</i>, which you could them move to <i>/etc</i> and tailor to your needs, but apparently I'm wrong about that. I was then going to write in the next section about using a systemd unit to mount the Drive, but after I wrote most of this I realized it also would mount the Drive at boot-up, regardless of the owner being logged in, unless more hoops were jumped through to get all the t's crossed and all the i's dotted.<br /><br /></li><li>Other methods such as a cron job to mount the drive on login, etc, but the above methods are probably your best bet.<br /></li></ol></ol>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-87012899954159655402023-10-14T16:34:00.001-05:002023-10-14T16:34:14.934-05:00Understanding "Church"<p>I normally don't like to link to outside content for fear of bit-rot, but this article is worth pointing to, and I don't want to just copy/paste the contents here and potentially take away from their visits.<br /><br /><a href="https://woodlandhillsnashville.com/topical/understanding-church/understanding-church.html">https://woodlandhillsnashville.com/topical/understanding-church/understanding-church.html</a><br /></p>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-49298588970983643652023-09-23T16:32:00.007-05:002023-09-23T16:32:32.747-05:00<p> </p><div data-contents="true"><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="b6fi3-0-0"><div class="_1mf _1mj" data-offset-key="b6fi3-0-0"><span class="xv78j7m" data-offset-key="b6fi3-0-0" spellcheck="false" start="0"><span data-offset-key="b6fi3-0-0"><span data-text="true">Nathan Cox</span></span></span><span data-offset-key="b6fi3-1-0"><span data-text="true"> had recommended a video. It's a good lesson, but with bad audio that made it very hard for me to follow. However, I made notes that I believe got the gist of the presentation, and share them here.</span></span></div><div class="_1mf _1mj" data-offset-key="b6fi3-0-0"><span data-offset-key="b6fi3-1-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="b6fi3-0-0"><span data-offset-key="b6fi3-1-0"><span data-text="true"> </span></span><span class="x1fey0fg"><span data-offset-key="b6fi3-2-0"><span data-text="true">https://www.youtube.com/watch?v=9SJ4h8qy5mg</span></span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="cqt2n-0-0"><div class="_1mf _1mj" data-offset-key="cqt2n-0-0"><span data-offset-key="cqt2n-0-0"><br data-text="true" /></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="8vcus-0-0"><div class="_1mf _1mj" data-offset-key="8vcus-0-0"><span data-offset-key="8vcus-0-0"><span data-text="true">A lot of guys preach to have a job.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="2kigc-0-0"><div class="_1mf _1mj" data-offset-key="2kigc-0-0"><span data-offset-key="2kigc-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="2kigc-0-0"><span data-offset-key="2kigc-0-0"><span data-text="true">My job's to stir up the ant hill. And leave town.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9j612-0-0"><div class="_1mf _1mj" data-offset-key="9j612-0-0"><span data-offset-key="9j612-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="9j612-0-0"><span data-offset-key="9j612-0-0"><span data-text="true">Before becoming a Christian, I was an atheist.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="bl4ur-0-0"><div class="_1mf _1mj" data-offset-key="bl4ur-0-0"><span data-offset-key="bl4ur-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="bl4ur-0-0"><span data-offset-key="bl4ur-0-0"><span data-text="true">A concept I was impressed with was "Calling Bible things by Bible Names", which comes out of 1 Cor 2, "words taught by the Holy Spirit".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="25qs4-0-0"><div class="_1mf _1mj" data-offset-key="25qs4-0-0"><span data-offset-key="25qs4-0-0"><span data-text="true">[</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ds0v1-0-0"><div class="_1mf _1mj" data-offset-key="ds0v1-0-0"><span data-offset-key="ds0v1-0-0"><span data-text="true">13 We also speak these things, not in words which man’s wisdom teaches, but which the Holy Spirit teaches, comparing spiritual things with spiritual things.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="srln-0-0"><div class="_1mf _1mj" data-offset-key="srln-0-0"><span data-offset-key="srln-0-0"><span data-text="true">]</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="db948-0-0"><div class="_1mf _1mj" data-offset-key="db948-0-0"><span data-offset-key="db948-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="db948-0-0"><span data-offset-key="db948-0-0"><span data-text="true">He calls it an "immersistry" instead of a"baptistry".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="5base-0-0"><div class="_1mf _1mj" data-offset-key="5base-0-0"><span data-offset-key="5base-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="5base-0-0"><span data-offset-key="5base-0-0"><span data-text="true">He was asked if he used a piano. Saying "yes", he was challenged to look into the scriptures. They quit using the piano until they could do some research. He studied the Greek, etc, and came to conclude that when somebody says, "The Greek says", you better be wary.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="f3g23-0-0"><div class="_1mf _1mj" data-offset-key="f3g23-0-0"><span data-offset-key="f3g23-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="f3g23-0-0"><span data-offset-key="f3g23-0-0"><span data-text="true">Talking to a CoC preacher, he wondered why it was okay for the preacher's young girls to listen to instrumental "Crocodile Rock", but a stringed instrumental version of "The Old Rugged Cross" was not okay; it didn't make sense to him.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="64cdk-0-0"><div class="_1mf _1mj" data-offset-key="64cdk-0-0"><span data-offset-key="64cdk-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="64cdk-0-0"><span data-offset-key="64cdk-0-0"><span data-text="true">That led to Heb 13:15....</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9oav2-0-0"><div class="_1mf _1mj" data-offset-key="9oav2-0-0"><span data-offset-key="9oav2-0-0"><span data-text="true">[</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="clhrb-0-0"><div class="_1mf _1mj" data-offset-key="clhrb-0-0"><span data-offset-key="clhrb-0-0"><span data-text="true">15 Through him, then, let’s offer up a sacrifice of praise to God continually, that is, the fruit of lips which proclaim allegiance to his name.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="5bper-0-0"><div class="_1mf _1mj" data-offset-key="5bper-0-0"><span data-offset-key="5bper-0-0"><span data-text="true">]</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="32ek0-0-0"><div class="_1mf _1mj" data-offset-key="32ek0-0-0"><span data-offset-key="32ek0-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="32ek0-0-0"><span data-offset-key="32ek0-0-0"><span data-text="true">And 1 Cor 16...</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="7c704-0-0"><div class="_1mf _1mj" data-offset-key="7c704-0-0"><span data-offset-key="7c704-0-0"><span data-text="true"> [</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ahjrd-0-0"><div class="_1mf _1mj" data-offset-key="ahjrd-0-0"><span data-offset-key="ahjrd-0-0"><span data-text="true">Now concerning the collection for the saints, as I commanded the assemblies of Galatia, you do likewise. 2 On the first day of every week, let each one of you save, as he may prosper, that no collections are made when I come. 3 When I arrive, I will send whoever you approve with letters to carry your gracious gift to Jerusalem. 4 If it is appropriate for me to go also, they will go with me.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="calmq-0-0"><div class="_1mf _1mj" data-offset-key="calmq-0-0"><span data-offset-key="calmq-0-0"><span data-text="true">]</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="8sqvs-0-0"><div class="_1mf _1mj" data-offset-key="8sqvs-0-0"><span data-offset-key="8sqvs-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="8sqvs-0-0"><span data-offset-key="8sqvs-0-0"><span data-text="true">He asked if there was any scripture that supported the idea of a collection in worship for the local church; this passage concerns a special collection for a distant church.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="461kk-0-0"><div class="_1mf _1mj" data-offset-key="461kk-0-0"><span data-offset-key="461kk-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="461kk-0-0"><span data-offset-key="461kk-0-0"><span data-text="true">Everyone's asking the question about instrumental music in worship; maybe we should ask for the New Testament definition of "worship".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="1dv0a-0-0"><div class="_1mf _1mj" data-offset-key="1dv0a-0-0"><span data-offset-key="1dv0a-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="1dv0a-0-0"><span data-offset-key="1dv0a-0-0"><span data-text="true">"That'd probably be a good question, wouldn't it? If we're gonna call Bible things by Bible names, well then let's, let's get this definition figured out. ... Let the Bible define it."</span></span></div><div class="_1mf _1mj" data-offset-key="1dv0a-0-0"><span data-offset-key="1dv0a-0-0"><span data-text="true"> </span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="6om76-0-0"><div class="_1mf _1mj" data-offset-key="6om76-0-0"><span data-offset-key="6om76-0-0"><span data-text="true">The particular Greek word is </span></span><span data-offset-key="6om76-0-1" style="font-style: italic;"><span data-text="true">proskenueo</span></span><span data-offset-key="6om76-0-2"><span data-text="true">. Alexander the Great, as Emperor; the Persians bowed their faces to the ground, and Alexander kindda liked that. He tried to get his loyal Macedonian cavalry to do the same thing, and they wouldn't do it, because they thought </span></span><span data-offset-key="6om76-0-3" style="font-style: italic;"><span data-text="true">proskenueo</span></span><span data-offset-key="6om76-0-4"><span data-text="true"> was reserved for the gods, and they knew Alexander was just a man.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="eckf6-0-0"><div class="_1mf _1mj" data-offset-key="eckf6-0-0"><span data-offset-key="eckf6-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="eckf6-0-0"><span data-offset-key="eckf6-0-0"><span data-text="true">Heb 11:</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="jm7i-0-0"><div class="_1mf _1mj" data-offset-key="jm7i-0-0"><span data-offset-key="jm7i-0-0"><span data-text="true">[</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="8rsrm-0-0"><div class="_1mf _1mj" data-offset-key="8rsrm-0-0"><span data-offset-key="8rsrm-0-0"><span data-text="true">21 By faith, Jacob, when he was dying, blessed each of the sons of Joseph, and worshiped [</span></span><span data-offset-key="8rsrm-0-1" style="font-style: italic;"><span data-text="true">proskeneuo</span></span><span data-offset-key="8rsrm-0-2"><span data-text="true">], leaning on the top of his staff.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="88rlq-0-0"><div class="_1mf _1mj" data-offset-key="88rlq-0-0"><span data-offset-key="88rlq-0-0"><span data-text="true">]</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9ma05-0-0"><div class="_1mf _1mj" data-offset-key="9ma05-0-0"><span data-offset-key="9ma05-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="9ma05-0-0"><span data-offset-key="9ma05-0-0"><span data-text="true">Patriarchs - Adam-Moses - Worship 1.0</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="e9ier-0-0"><div class="_1mf _1mj" data-offset-key="e9ier-0-0"><span data-offset-key="e9ier-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="e9ier-0-0"><span data-offset-key="e9ier-0-0"><span data-text="true">Law of Moses - Moses to death of Christ - Worship 2.0</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="4eh7c-0-0"><div class="_1mf _1mj" data-offset-key="4eh7c-0-0"><span data-offset-key="4eh7c-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="4eh7c-0-0"><span data-offset-key="4eh7c-0-0"><span data-text="true">Death of Christ forward - New Covenant - Worship 3.0</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="1m69p-0-0"><div class="_1mf _1mj" data-offset-key="1m69p-0-0"><span data-offset-key="1m69p-0-0" style="font-style: italic;"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="1m69p-0-0"><span data-offset-key="1m69p-0-0" style="font-style: italic;"><span data-text="true">proskeneuo</span></span><span data-offset-key="1m69p-0-1"><span data-text="true"> is a physical act, of bowing. This was Worship 1.0</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="f6gdi-0-0"><div class="_1mf _1mj" data-offset-key="f6gdi-0-0"><span data-offset-key="f6gdi-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="f6gdi-0-0"><span data-offset-key="f6gdi-0-0"><span data-text="true">Worship 2.0 is an upgrade to become a spiritual act.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9hap9-0-0"><div class="_1mf _1mj" data-offset-key="9hap9-0-0"><span data-offset-key="9hap9-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="9hap9-0-0"><span data-offset-key="9hap9-0-0"><span data-text="true">A Principle of scripture: first the physical, then the spiritual.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9v6ru-0-0"><div class="_1mf _1mj" data-offset-key="9v6ru-0-0"><span data-offset-key="9v6ru-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="9v6ru-0-0"><span data-offset-key="9v6ru-0-0"><span data-text="true">Worship 2.0 - John 4:24 - The only place a Jew can worship is in the temple, and the only time a Jew can worship is on the feast days. The synagogue meeting on the Sabbath was not for worship, but for reading of the law and the prophets (Acts 13 - 15 After the reading of the law and the prophets, the rulers of the synagogue sent to them, saying, “Brothers, if you have any word of exhortation for the people, speak.”).</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="cidr3-0-0"><div class="_1mf _1mj" data-offset-key="cidr3-0-0"><span data-offset-key="cidr3-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="cidr3-0-0"><span data-offset-key="cidr3-0-0"><span data-text="true">"And you see that terminology all the way through."</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="bf1qn-0-0"><div class="_1mf _1mj" data-offset-key="bf1qn-0-0"><span data-offset-key="bf1qn-0-0"><span data-text="true">Ex: John 12:20 "They are going up to worship at the feast."</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ancvm-0-0"><div class="_1mf _1mj" data-offset-key="ancvm-0-0"><span data-offset-key="ancvm-0-0"><span data-text="true">Ex: Acts 8 "[The Ethiopian" had come to Jerusalem to worship."</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="7ih7f-0-0"><div class="_1mf _1mj" data-offset-key="7ih7f-0-0"><span data-offset-key="7ih7f-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="7ih7f-0-0"><span data-offset-key="7ih7f-0-0"><span data-text="true">That's why the woman at the well said , "You Jews say we must worship at Jerusalem."</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="g0s9-0-0"><div class="_1mf _1mj" data-offset-key="g0s9-0-0"><span data-offset-key="g0s9-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="g0s9-0-0"><span data-offset-key="g0s9-0-0"><span data-text="true">Worship 2.0 = at the temple, during feasts; go up and participate in all the doings. The priests serve; the people bow in the presence of the Lord, in the temple, singing the Songs of Ascent as they wind their way up the stairs to the courtyard on the temple mount - "I was glad when they said to me, 'Let’s go to Yahweh’s house!'” - Psalm 122:1).</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="d9811-0-0"><div class="_1mf _1mj" data-offset-key="d9811-0-0"><span data-offset-key="d9811-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="d9811-0-0"><span data-offset-key="d9811-0-0"><span data-text="true">Worship 3.0</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="en2hb-0-0"><div class="_1mf _1mj" data-offset-key="en2hb-0-0"><span data-offset-key="en2hb-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="en2hb-0-0"><span data-offset-key="en2hb-0-0"><span data-text="true">Worship is the most important thing in life. The "covering cherub"? wanted worship so badly he began a rebellion that is going on still today.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="nvlb-0-0"><div class="_1mf _1mj" data-offset-key="nvlb-0-0"><span data-offset-key="nvlb-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="nvlb-0-0"><span data-offset-key="nvlb-0-0"><span data-text="true">The only place that talks about "new covenant worship" is John 4:23-24.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ecibp-0-0"><div class="_1mf _1mj" data-offset-key="ecibp-0-0"><span data-offset-key="ecibp-0-0"><span data-text="true">"in spirit and in truth"</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="u68c-0-0"><div class="_1mf _1mj" data-offset-key="u68c-0-0"><span data-offset-key="u68c-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="u68c-0-0"><span data-offset-key="u68c-0-0"><span data-text="true">There's an outer-man and an inner-man. We live according to the inner-man. This is the spirit - the inner-man.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="5q2so-0-0"><div class="_1mf _1mj" data-offset-key="5q2so-0-0"><span data-offset-key="5q2so-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="5q2so-0-0"><span data-offset-key="5q2so-0-0"><span data-text="true">What are you going to put your emphasis on? That which is decaying, or that which isn't?</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ebd0c-0-0"><div class="_1mf _1mj" data-offset-key="ebd0c-0-0"><span data-offset-key="ebd0c-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="ebd0c-0-0"><span data-offset-key="ebd0c-0-0"><span data-text="true">We pay a lot of attention to the outer-man, but it's decaying.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="cdcjb-0-0"><div class="_1mf _1mj" data-offset-key="cdcjb-0-0"><span data-offset-key="cdcjb-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="cdcjb-0-0"><span data-offset-key="cdcjb-0-0"><span data-text="true">What are you going to put your emphasis on? What's God gonna put his emphasizes on?</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="1qeuh-0-0"><div class="_1mf _1mj" data-offset-key="1qeuh-0-0"><span data-offset-key="1qeuh-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="1qeuh-0-0"><span data-offset-key="1qeuh-0-0"><span data-text="true">The spirit of God lives in our carcass, having cleaned up the inside in order to move in.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="1975b-0-0"><div class="_1mf _1mj" data-offset-key="1975b-0-0"><span data-offset-key="1975b-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="1975b-0-0"><span data-offset-key="1975b-0-0"><span data-text="true">The veil of the temple was torn, making the temple one room.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="8cq4g-0-0"><div class="_1mf _1mj" data-offset-key="8cq4g-0-0"><span data-offset-key="8cq4g-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="8cq4g-0-0"><span data-offset-key="8cq4g-0-0"><span data-text="true">The Bible reveals to us the realm we can't see, the spiritual realm.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="17ugt-0-0"><div class="_1mf _1mj" data-offset-key="17ugt-0-0"><span data-offset-key="17ugt-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="17ugt-0-0"><span data-offset-key="17ugt-0-0"><span data-text="true">We're ushered into the one room of the temple by the blood of Jesus.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="f9gk9-0-0"><div class="_1mf _1mj" data-offset-key="f9gk9-0-0"><span data-offset-key="f9gk9-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="fgll2-0-0"><span data-offset-key="fgll2-0-0"><span data-text="true">Heb 10:19 Having therefore, brothers, boldness to enter into the holy place by the blood of Jesus, 20 by the way which he dedicated for us, a new and living way, through the veil, that is to say, his flesh, 21 and having a great priest over God’s house, 22 let’s draw near with a true heart in fullness of faith, having our hearts sprinkled from an evil conscience, and having our body washed with pure water, 23 let’s hold fast the confession of our hope without wavering; for he who promised is faithful.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="21hv6-0-0"><div class="_1mf _1mj" data-offset-key="21hv6-0-0"><span data-offset-key="21hv6-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="21hv6-0-0"><span data-offset-key="21hv6-0-0"><span data-text="true">When our bodies are washed, our hearts are sprinkled. We are given immediate, instant, and present access, because we're in the presence of God.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="45iu9-0-0"><div class="_1mf _1mj" data-offset-key="45iu9-0-0"><span data-offset-key="45iu9-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="45iu9-0-0"><span data-offset-key="45iu9-0-0"><span data-text="true">Any mental image you have, Ezekiel or whatever, of someone being in the presence of God, what is it? </span></span><span data-offset-key="45iu9-0-1" style="font-style: italic;"><span data-text="true">Proskeneuo</span></span><span data-offset-key="45iu9-0-2"><span data-text="true">.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="aq7ep-0-0"><div class="_1mf _1mj" data-offset-key="aq7ep-0-0"><span data-offset-key="aq7ep-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="aq7ep-0-0"><span data-offset-key="aq7ep-0-0"><span data-text="true">"The point is... our inner-person is in perpetual <i>proskuneo</i> in the presence of him who is and was and is to come. And that's Worship 3.0."</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="5cmnp-0-0"><div class="_1mf _1mj" data-offset-key="5cmnp-0-0"><span data-offset-key="5cmnp-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="5cmnp-0-0"><span data-offset-key="5cmnp-0-0"><span data-text="true">Can you show me any place in the New Testament where the church game together to worship?</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="dop47-0-0"><div class="_1mf _1mj" data-offset-key="dop47-0-0"><span data-offset-key="dop47-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="dop47-0-0"><span data-offset-key="dop47-0-0"><span data-text="true">I can understand how the Lord's Supper might be considered worship.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="5cpnv-0-0"><div class="_1mf _1mj" data-offset-key="5cpnv-0-0"><span data-offset-key="5cpnv-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="5cpnv-0-0"><span data-offset-key="5cpnv-0-0"><span data-text="true">But there's another word, </span></span><span data-offset-key="5cpnv-0-1" style="font-style: italic;"><span data-text="true">latreo</span></span><span data-offset-key="5cpnv-0-2"><span data-text="true">, and that has to do with "service".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="428t4-0-0"><div class="_1mf _1mj" data-offset-key="428t4-0-0"><span data-offset-key="428t4-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="428t4-0-0"><span data-offset-key="428t4-0-0"><span data-text="true">In the Old Testament, the priests served, and the people worshiped. So collectively, the people of Israel worshiped and served.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="1t16h-0-0"><div class="_1mf _1mj" data-offset-key="1t16h-0-0"><span data-offset-key="1t16h-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="1t16h-0-0"><span data-offset-key="1t16h-0-0"><span data-text="true">King Uzziah gets too big for his britches, and says, "I want to waltz into the temple, and I want to burn incense to the Lord." The priests resisted, but he took the censor in hand and walked across the threshold, and leprosy broke out all over him. The priests hustled him out into isolation, where he remained the rest of his life. [Not quite an accurate retelling, but the gist is correct. 2 Chron 26]</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ap7sp-0-0"><div class="_1mf _1mj" data-offset-key="ap7sp-0-0"><span data-offset-key="ap7sp-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="ap7sp-0-0"><span data-offset-key="ap7sp-0-0"><span data-text="true">The message is that only the priests offer service.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="2acuu-0-0"><div class="_1mf _1mj" data-offset-key="2acuu-0-0"><span data-offset-key="2acuu-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="2acuu-0-0"><span data-offset-key="2acuu-0-0"><span data-text="true">Sacrifice is service.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="bvbsv-0-0"><div class="_1mf _1mj" data-offset-key="bvbsv-0-0"><span data-offset-key="bvbsv-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="bvbsv-0-0"><span data-offset-key="bvbsv-0-0"><span data-text="true"><mumble mumble Christians mumble people <mumble> priests mumble></span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="2s8qu-0-0"><div class="_1mf _1mj" data-offset-key="2s8qu-0-0"><span data-offset-key="2s8qu-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="2s8qu-0-0"><span data-offset-key="2s8qu-0-0"><span data-text="true">Rom 12:1 Therefore I urge you, brothers, by the mercies of God, to present your bodies a living sacrifice, holy, acceptable to God, which is your spiritual service.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="2tvbm-0-0"><div class="_1mf _1mj" data-offset-key="2tvbm-0-0"><span data-offset-key="2tvbm-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="2tvbm-0-0"><span data-offset-key="2tvbm-0-0"><span data-text="true"><Thanuel's Big Boys? added "worship" ... could do that?> But they did it.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="5e4ks-0-0"><div class="_1mf _1mj" data-offset-key="5e4ks-0-0"><span data-offset-key="5e4ks-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="5e4ks-0-0"><span data-offset-key="5e4ks-0-0"><span data-text="true">"Old King James does the best" at rendering the word for "service".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9k3rl-0-0"><div class="_1mf _1mj" data-offset-key="9k3rl-0-0"><span data-offset-key="9k3rl-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="9k3rl-0-0"><span data-offset-key="9k3rl-0-0"><span data-text="true">The New Testament priest offers his body in service to God. Everything external is service. Internal is "worship". External is "service". On the inside are "the people"; on the outside are "the priests".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="9n8f4-0-0"><div class="_1mf _1mj" data-offset-key="9n8f4-0-0"><span data-offset-key="9n8f4-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="9n8f4-0-0"><span data-offset-key="9n8f4-0-0"><span data-text="true"><So let me kinks then mumble?> about singing, praying, giving, and Lord's Supper. When we start doing those things, is that going to be "service"? Or is it going to be "worship"?</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="b3n8q-0-0"><div class="_1mf _1mj" data-offset-key="b3n8q-0-0"><span data-offset-key="b3n8q-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="b3n8q-0-0"><span data-offset-key="b3n8q-0-0"><span data-text="true">My guess would be, "service". Let's check it out.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="12hhk-0-0"><div class="_1mf _1mj" data-offset-key="12hhk-0-0"><span data-offset-key="12hhk-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="12hhk-0-0"><span data-offset-key="12hhk-0-0"><span data-text="true">Heb 13:15 ("which we quoted earlier") 15 Through him, then, let’s offer up a sacrifice of praise to God continually, that is, the fruit of lips which proclaim allegiance to his name.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="cie8q-0-0"><div class="_1mf _1mj" data-offset-key="cie8q-0-0"><span data-offset-key="cie8q-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="cie8q-0-0"><span data-offset-key="cie8q-0-0"><span data-text="true">"Sacrifice"; that's what a priest does; that's "service".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="d1anj-0-0"><div class="_1mf _1mj" data-offset-key="d1anj-0-0"><span data-offset-key="d1anj-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="d1anj-0-0"><span data-offset-key="d1anj-0-0"><span data-text="true">16 But don’t forget to be doing good and sharing, for with such sacrifices God is well pleased.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="31kfh-0-0"><div class="_1mf _1mj" data-offset-key="31kfh-0-0"><span data-offset-key="31kfh-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="31kfh-0-0"><span data-offset-key="31kfh-0-0"><span data-text="true">"Doing the Lord's work" and everything that goes along with it: "service".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="3s58n-0-0"><div class="_1mf _1mj" data-offset-key="3s58n-0-0"><span data-offset-key="3s58n-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="3s58n-0-0"><span data-offset-key="3s58n-0-0"><span data-text="true">How about "preaching"? That's one of the so-called five channels of public worship. <mumble where words come from some time.> They come from the same place as "rapture" and "accept Jesus into your heart", is where they come from.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="ep73p-0-0"><div class="_1mf _1mj" data-offset-key="ep73p-0-0"><span data-offset-key="ep73p-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="ep73p-0-0"><span data-offset-key="ep73p-0-0"><span data-text="true">Rom 15:15 But I write the more boldly to you in part, as reminding you, because of the grace that was given to me by God, 16 that I should be a servant of Christ Jesus to the Gentiles, serving as a priest of the Good News of God, that the offering up of the Gentiles might be made acceptable, sanctified by the Holy Spirit.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="4hhki-0-0"><div class="_1mf _1mj" data-offset-key="4hhki-0-0"><span data-offset-key="4hhki-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="4hhki-0-0"><span data-offset-key="4hhki-0-0"><span data-text="true">See, "preaching" is "service".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="dr5n6-0-0"><div class="_1mf _1mj" data-offset-key="dr5n6-0-0"><span data-offset-key="dr5n6-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="545ds-0-0"><span data-offset-key="545ds-0-0"><span data-text="true">1 Cor 10:14 Therefore, my beloved, flee from idolatry. 15 I speak as to wise men. Judge what I say. 16 The cup of blessing which we bless, isn’t it a sharing of the blood of Christ? The bread which we break, isn’t it a sharing of the body of Christ? 17 Because there is one loaf of bread, we, who are many, are one body; for we all partake of the one loaf of bread. 18 Consider Israel according to the flesh. Don’t those who eat the sacrifices participate in the altar?</span></span></div><div class="_1mf _1mj" data-offset-key="545ds-0-0"><span data-offset-key="545ds-0-0"><span data-text="true"> </span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="498sa-0-0"><div class="_1mf _1mj" data-offset-key="498sa-0-0"><span data-offset-key="498sa-0-0"><span data-text="true">Whazzat? <mumble> priest.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="mira-0-0"><div class="_1mf _1mj" data-offset-key="mira-0-0"><span data-offset-key="mira-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="mira-0-0"><span data-offset-key="mira-0-0"><span data-text="true">Eating food sacrificed is a participation in the sacrifice. Personal anecdote about altar in Russia.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="8ortg-0-0"><div class="_1mf _1mj" data-offset-key="8ortg-0-0"><span data-offset-key="8ortg-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="8ortg-0-0"><span data-offset-key="8ortg-0-0"><span data-text="true">Heb 13:10 We have an altar from which those who serve the holy tabernacle have no right to eat.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="57kjj-0-0"><div class="_1mf _1mj" data-offset-key="57kjj-0-0"><span data-offset-key="57kjj-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="57kjj-0-0"><span data-offset-key="57kjj-0-0"><span data-text="true">My point is that the Lord's Supper is "service". It's a sacrifice participated in by new covenant priests. The old covenant priests are out; they don't get in.</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="baqb7-0-0"><div class="_1mf _1mj" data-offset-key="baqb7-0-0"><span data-offset-key="baqb7-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="baqb7-0-0"><span data-offset-key="baqb7-0-0"><span data-text="true">We "know"...</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="180bb-0-0"><div class="_1mf _1mj" data-offset-key="180bb-0-0"><span data-offset-key="180bb-0-0"><span data-text="true">- prayer</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="elg1d-0-0"><div class="_1mf _1mj" data-offset-key="elg1d-0-0"><span data-offset-key="elg1d-0-0"><span data-text="true">- singing</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="98e0g-0-0"><div class="_1mf _1mj" data-offset-key="98e0g-0-0"><span data-offset-key="98e0g-0-0"><span data-text="true">- collection</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="1lefh-0-0"><div class="_1mf _1mj" data-offset-key="1lefh-0-0"><span data-offset-key="1lefh-0-0"><span data-text="true">- preaching</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="qar4-0-0"><div class="_1mf _1mj" data-offset-key="qar4-0-0"><span data-offset-key="qar4-0-0"><span data-text="true">- the Lord's Supper</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="6aff0-0-0"><div class="_1mf _1mj" data-offset-key="6aff0-0-0"><span data-offset-key="6aff0-0-0"><span data-text="true">... is worship, don't we?</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="43ild-0-0"><div class="_1mf _1mj" data-offset-key="43ild-0-0"><span data-offset-key="43ild-0-0"><span data-text="true"> How do we "know" that? There's not a verse in scripture that says any of that is "worship".</span></span></div></div><div class="x1e56ztr" data-block="true" data-editor="crk25" data-offset-key="f33uj-0-0"><div class="_1mf _1mj" data-offset-key="f33uj-0-0"><span data-offset-key="f33uj-0-0"><span data-text="true"> </span></span></div><div class="_1mf _1mj" data-offset-key="f33uj-0-0"><span data-offset-key="f33uj-0-0"><span data-text="true"><False formility is a false dot.> False terminology creates false <concepts?>.</span></span></div></div></div>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-20853156756357093942023-09-18T08:27:00.001-05:002023-09-18T08:27:09.818-05:00<h2>The following is written by <i>Brian Zahnd</i>.</h2><br />I have a problem with the Bible. Here’s my problem…<br /> <br />I’m an ancient Egyptian. I’m a comfortable Babylonian. I’m a Roman in his villa.<br /> <br />That’s my problem. See, I’m trying to read the Bible for all it’s worth, but I’m not a Hebrew slave suffering in Egypt. I’m not a conquered Judean deported to Babylon. I’m not a first century Jew living under Roman occupation.<br /> <br />I’m a citizen of a superpower. I was born among the conquerors. I live in the empire. But I want to read the Bible and think it’s talking to me. This is a problem.<br /> <br />One of the most remarkable things about the Bible is that in it we find the narrative told from the perspective of the poor, the oppressed, the enslaved, the conquered, the occupied, the defeated. This is what makes it prophetic. We know that history is written by the winners. This is true — except in the case of the Bible it’s the opposite! This is the subversive genius of the Hebrew prophets. They wrote from a bottom-up perspective.<br /> <br />Imagine a history of colonial America written by Cherokee Indians and African slaves. That would be a different way of telling the story! And that’s what the Bible does. It’s the story of Egypt told by the slaves. The story of Babylon told by the exiles. The story of Rome told by the occupied. What about those brief moments when Israel appeared to be on top? In those cases the prophets told Israel’s story from the perspective of the peasant poor as a critique of the royal elite. Like when Amos denounced the wives of the Israelite aristocracy as “the fat cows of Bashan.”<br /> <br />Every story is told from a vantage point; it has a bias. The bias of the Bible is from the vantage point of the underclass. But what happens if we lose sight of the prophetically subversive vantage point of the Bible? What happens if those on top read themselves into the story, not as imperial Egyptians, Babylonians, and Romans, but as the Israelites? That’s when you get the bizarre phenomenon of the elite and entitled using the Bible to endorse their dominance as God’s will. This is Roman Christianity after Constantine. This is Christendom on crusade. This is colonists seeing America as their promised land and the native inhabitants as Canaanites to be conquered. This is the whole history of European colonialism. This is Jim Crow. This is the American prosperity gospel. This is the domestication of Scripture. This is making the Bible dance a jig for our own amusement.<br /> <br />As Jesus preached the arrival of the kingdom of God he would frequently emphasize the revolutionary character of God’s reign by saying things like, “the last will be first and the first last.” How does Jesus’ first-last aphorism strike you? I don’t know about you, but it makes this modern day Roman a bit nervous.<br /> <br />Imagine this: A powerful charismatic figure arrives on the world scene and amasses a great following by announcing the arrival of a new arrangement of the world where those at the bottom are to be promoted and those on top are to have their lifestyle “restructured.” How do people receive this? I can imagine the Bangladeshis saying, “When do we start?!” and the Americans saying, “Hold on now, let’s not get carried away!”<br /> <br />Now think about Jesus announcing the arrival of God’s kingdom with the proclamation of his counterintuitive Beatitudes. When Jesus said, “Blessed are the meek, for they shall inherit the earth,” how was that received? Well, it depends on who is hearing it. The poor Galilean peasant would hear it as good news (gospel), while the Roman in his villa would hear it with deep suspicion. (I know it’s an anachronism, but I can imagine Claudius saying something like, “sounds like socialism to me!”)<br /> <br />And that’s the challenge I face in reading the Bible. I’m not the Galilean peasant. Who am I kidding! I’m the Roman in his villa and I need to be honest about it. I too can hear the gospel of the kingdom as good news (because it is!), but first I need to admit its radical nature and not try to tame it to endorse my inherited entitlement.<br /> <br />I am a (relatively) wealthy white American male. Which is fine, but it means I have to work hard at reading the Bible right. I have to see myself basically as aligned with Pharaoh, Nebuchadnezzar, and Caesar. In that case, what does the Bible ask of me? Voluntary poverty? Not necessarily. But certainly the Bible calls me to deep humility — a humility demonstrated in hospitality and generosity. There’s nothing necessarily wrong with being a relatively well-off white American male, but I better be humble, hospitable, and generous!<br /> <br />If I read the Bible with the appropriate perspective and humility I don’t use the story of the Rich Man and Lazarus as a proof-text to condemn others to hell. I use it as a reminder that I’m a rich man and Lazarus lies at my door. I don’t use the conquest narratives of Joshua to justify Manifest Destiny. Instead I see myself as a Rahab who needs to welcome newcomers. I don’t fancy myself as Elijah calling down fire from heaven. I’m more like Nebuchadnezzar who needs to humble himself lest I go insane.<br /> <br />I have a problem with the Bible, but all is not lost. I just need to read it standing on my head. I need to change my perspective. If I can accept that the Bible is trying to lift up those who are unlike me, then perhaps I can read the Bible right.”<br /><br />Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-58585014963574718852023-07-25T12:49:00.002-05:002023-07-25T12:49:12.482-05:00Old Covenant vs New<p><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x6prxxf xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto">As
per Jeremiah 31, the difference between the old and new covenants is
that the first consisted of God's law written on stone tablets, whereas
the second consists of God's law written in the inward parts and in the
heart.</span></p>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-31134865937532845872023-07-25T12:33:00.004-05:002023-08-01T08:25:55.003-05:00Ten Commandments Nailed to the Cross?<p>It is a common misunderstanding that Col 2:14-17 says that the Ten Commandments were nailed to the cross. But that's not what Col 2:14-17 says. Because of the similarity in wording between this passage and the passage in Ephesians chapter 2, we need to look at both, starting with the Ephesians passage. But before getting to that, let's look at the word "ordinances".</p><h3 style="text-align: left;">The Word "Ordinances"<br /></h3><p>The word "ordinances" (<i>dogmasin</i>) in v. Col 2:14 is used in the "New Testament" five times, three of which clearly refer to human-uttered decrees, not God-uttered ones, and two of them being found here and in the sister passage of Eph 2:15. In the Greek Septuagint version of the "Old Testament", this word is only used in the book of Daniel, and only to refer to human decrees, not decrees from God. (A different word, <i>dikaioma</i>, is used ten times in the NT, and refers to God-uttered decrees and righteousness.) Paul knew his Greek, and he knew his Greek scriptures, and it seems odd that he would use the word "ordinances" to refer to God-uttered ordinances when it seems to only be used every where else in the scriptures for human-uttered ordinances.<br /></p><h3 style="text-align: left;">The Ephesians Passage</h3><blockquote>WEB Eph 2:11 Therefore remember that once you, the Gentiles in the flesh, who are called “uncircumcision” by that which is called “circumcision” (in the flesh, made by hands), 12 that you were at that time separate from Christ, alienated from the commonwealth of Israel, and strangers from the covenants of the promise, having no hope and without God in the world. 13 But now in Christ Jesus you who once were far off are made near in the blood of Christ. 14 For he is our peace, who made both one, and broke down the middle wall of separation, <b>15 having abolished in his flesh the hostility, the law of commandments contained in ordinances, that he might create in himself one new man of the two, making peace,</b> 16 and might reconcile them both in one body to God through the cross, having killed the hostility through it. 17 He came and preached peace to you who were far off and to those who were near. 18 For through him we both have our access in one Spirit to the Father. 19 So then you are no longer strangers and foreigners, but you are fellow citizens with the saints and of the household of God, 20 being built on the foundation of the apostles and prophets, Christ Jesus himself being the chief cornerstone; 21 in whom the whole building, fitted together, grows into a holy temple in the Lord; 22 in whom you also are built together for a habitation of God in the<span class="text Eph-2-22" id="en-WEB-29253"> Spirit.</span></blockquote><p><span class="text Eph-2-22" id="en-WEB-29253">If "ordinances" refers to human-uttered decrees rather than God-uttered ones, then Paul is saying here that whatever is <i>abolished</i> is contained in human-uttered decrees. <br /></span></p><p><span class="text Eph-2-22" id="en-WEB-29253">Whatever was <i>abolished</i> was abolished for the purpose of making one new man out of the two (Jew and Gentile), as well as for the purpose of reconciling that one new man with God. So whatever the <i>hostility</i> is that was abolished, it kept a middle wall of separation between Jew and Gentile. The God-uttered law of Moses (and/or the Ten Commandments) did not create this separation; in fact, the law repeatedly enjoins the Jew to treat the non-Jew as one of their own.</span></p><p><span class="text Eph-2-22" id="en-WEB-29253">However, the human-uttered Oral Law of the scribes and Pharisees did create this wall between Jew and non-Jew. Some of their ancient documents refer to this wall as a "fence", and interestingly enough, in the Greek of the phrase "middle wall of separation" is the word for "fence'. When Peter visited the non-Jew, God-fearing Cornelius, he opened the discussion by saying it was not lawful for a Jew to visit a non-Jew, but God had made him to understand that God did not agree with this "law"; this law can not be found in the God-uttered scriptures, but it can be found in the human-uttered Oral Law of the ancient Pharisees.<br /></span></p><h3 style="text-align: left;"> </h3><p>The "law of commandments contained in ordinances" phrase seems to refer to human-uttered Oral Law that separated Gentile from Jew. God's law never made this separation, but the Pharisees with their traditions did make the separation.</p><p>By drawing Gentile believers into the commonwealth of Israel, and making them part of the Israelite family of God, this "fence" no longer applied to them. The Oral Law, with its fence, it's middle wall of separation, is untouched, but the people whom it would have affected is no longer affected by it, because they are no longer foreigners.<br /></p><p>So in effect, the division, the hostility, the separation, enforced by these human-uttered ordinances was nailed to the cross, breaking down the middle wall of separation between Jew and Gentile. It was not a law, either the Oral Law or the Ten Commandments law, which was nailed to the cross, but rather the division between Jew and non-Jew that was nailed to the cross. This nailing is about peace between Jew and non-Jew, and then peace between that united new man and God; it is not about doing away with a legal system. On the cross, Jesus bore our sins against a legal system, not the legal system itself.<br /></p><h3 style="text-align: left;">The Colossians Passage</h3><blockquote><p>WEB Col 2:11 In him you were also circumcised with a circumcision not made with hands, in the putting off of the body of the sins of the flesh, in the circumcision of Christ, 12 having been buried with him in baptism, in which you were also raised with him through faith in the working of God, who raised him from the dead. 13 You were dead through your trespasses and the uncircumcision of your flesh. He made you alive together with him, <b>having forgiven us all our trespasses, 14 wiping out the handwriting in ordinances which was against us. He has taken it out of the way, nailing it to the cross. </b>15 Having stripped the principalities and the powers, he made a show of them openly, triumphing over them in it. <br /></p></blockquote><p>The phrase here in Colossians is similar to the phrase in Ephesians, but it turns out to be an ancient Jewish idiom. As many Bible translations now render it, it refers to a "criminal record", a list of the laws we have broken, an "IOU", or a "mortgage debt". The Phillips version, while not at all a literal rendering, colors the meaning in quite nicely: "Christ has utterly wiped out the damning evidence of broken laws and
commandments which always hung over our heads, and has completely
annulled it by nailing it over his own head on the cross"<span class="text Col-2-14" id="en-NASB-29496">.</span></p><h3 style="text-align: left;"><span class="text Col-2-14" id="en-NASB-29496">Conclusion <br /></span></h3><p>This passage, Col 2:14-17, is not telling us that the Ten Commandments have been nailed to the cross. It's telling us that that the legal charges against us in a court of law, which normally would be nailed above the head of the person being executed on a cross (as Jesus' "charges" were nailed above his head on his cross - <a href="https://www.biblegateway.com/passage/?search=luke+23%3A38&version=WEB" target="_blank">Luke 23:38</a>), were nailed above Jesus' head instead of ours, freeing us from the charges and placing them on him.<br /></p><p>The sister passage in Eph 2, also is not telling us that the Ten Commandments have been nailed to the cross. It's telling us that non-Jewish believers in the Messiah are just as Jewish as the native Jews, and that the separation between the two, as contained in human-uttered oral traditions, has been nailed to the cross.</p><p><br /></p><p><span style="font-size: xx-small;">Originally published at: <span><span class="w4txWc oJeWuf" id="c66" role="region"><span class="MUhG4e OGjyyf" data-blogurl="https://kentwest.blogspot.com/">https://kentwest.blogspot.com/2023/07/ten-commandments-nailed-to-cross.html</span></span></span></span></p>
<script src="https://www.biblegateway.com/public/link-to-us/tooltips/bglinks.js" type="text/javascript"></script>
<script type="text/javascript">
BGLinks.version = "WEB";
BGLinks.linkVerses();
</script>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-34259644366009384332023-06-16T12:43:00.006-05:002023-06-16T12:46:26.446-05:00Sign-Post to Jerusalem Church of Christ, circa 50 A.D.<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPpl1FRbVLkEVhp8BF6XuzyY9xqDe-S6Zu_-hxpxQ4uT_L705MrwtmaGd5UYHRjHMu6h2iQ88Jxhy2V0QES60LlLuR_kd77YWTnWNF62Vt2gOi6cVccb8611WOing-tCiRMsMoaQbXErfCBIfrTlxOGRjLUDW-bfgPX_B2z-2xKUmuU0zN/s1280/temple.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="Sign-post to Jerusalem Church of Chrits, circa 50 A.D." border="0" width="600" data-original-height="720" data-original-width="1280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPpl1FRbVLkEVhp8BF6XuzyY9xqDe-S6Zu_-hxpxQ4uT_L705MrwtmaGd5UYHRjHMu6h2iQ88Jxhy2V0QES60LlLuR_kd77YWTnWNF62Vt2gOi6cVccb8611WOing-tCiRMsMoaQbXErfCBIfrTlxOGRjLUDW-bfgPX_B2z-2xKUmuU0zN/s400/temple.png"/></a></div>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-67243966223155006182023-05-05T16:11:00.013-05:002023-05-05T16:34:27.970-05:00A Simple Introduction to CLAP in the Rust Programming Language<p>"CLAP" stands for <b><i>C</i></b>ommand-<b><i>L</i></b>ine <b><i>A</i></b>rgument <b><i>P</i></b>arser.</p>
<p>In order to get started using <i>clap</i>, you need to understand three things:</p>
<h3>1 - How to Run a Simple Rust Program</h3>
<p>Suppose you want to create a program called "fun". You should have a working installation of Rust and Cargo (the web is full of explanations of how to install this). In your file directory where you want to create the "fun" project:</p>
<p><code>$ cargo new fun</code></p>
<p><code>$ cargo run</code></p>
<p>This should print "Hello, World!", because every cargo-created project has this simple capability as a starting point.</p>
<h3>2 - How to Work with a <i>struct</i></h3>
<p>This is a little bit more advanced. Here's the "src/main.rs" program from our "cargo new fun" command above:</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>fn main()
println!("Hello, world!");
}
</pre>
</figure>
<p>A <i>struct</i>ure is simply a variable of a customized type, which often holds other variables. You should be familiar in a general way with variables, and know that they come in various kinds, or types: there are <i>String</i> variables and there are <i>i32</i> variables and there are <i>usize</i> variables, etc. A <i>struct</i> simply allows you to create your own customized variable type, and fill it with a collection of other variables of various types.</p>
<p>Say you have a variable called "last" which holds a person's last name, and a variable called "age" which holds that person's age in years. The first variable might be a type of <i>String</i> (which holds a string of text), and the second might be a type of <i>i8</i> (which holds an integer in the range of -128 to +127; a <i>u8</i>, which holds a range of 0 to 255, might be a better option).</p>
<p>Think of these two variables as two different types of fruit. We can carry them around in our hands if we like, or we can create a shopping bag to put them into. This shopping bag is analogous to a <i>struct</i>, except that a struct is well-defined as to what it can hold, whereas a shopping bag will hold just about anything pretty much, willy-nilly.</p>
<p>So a better way to think of it is as a custom-tool case, with exact-fitting compartments for the tools.</p>
<p>Example:</p>
<pre>
// This is the "master design" from which all toolboxes will be built.
struct toolbox_template {
owner: String, // It will belong to a specific person,
hammer: String, // and will hold a hammer,
screwdriver: i8, // and a screwdriver of a certain size.
}</pre>
<p>Note that this is just the definition of the "template" for a toolbox; it doesn't actually create a toolbox. Let's put this template into our "main.rs" file, along with the creation of two toolboxes based on this template, and then we'll print out a couple of messages about those tools. (We no longer need the "Hello, World!" println, so we'll delete it as per the <s>Strikethru</s> marking.)</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
struct ToolboxTemplate {
owner: String,
hammer: String,
screwdriver: i8,
}
fn main() {
<s> println!("Hello, world!");</s>
// Let's build a custom toolbox for Him.
let his_box: ToolboxTemplate = ToolboxTemplate {
owner: "Joe".to_string(),
hammer: "sledge".to_string(),
screwdriver: 8,
};
// And one for Her.
let her_box: ToolboxTemplate = ToolboxTemplate {
owner: "Jane".to_string(),
hammer: "pink-handled".to_string(),
screwdriver: 4
};
// Print the details of his toolbox.
println!("{} has a {} hammer and a Number {} screwdriver.",
his_box.owner, his_box.hammer, his_box.screwdriver
);
// And of hers.
println!("{} has a {} hammer and a Number {} screwdriver.",
her_box.owner, her_box.hammer, her_box.screwdriver
);
} // end of main()
</pre>
</figure>
<p>So you can see that a struct is just a custom-built variable, a "carrying case", that holds various other variables. The "struct" part defines the type, and then you have to create variables of that type which actually hold the desired data.</p>
<h3>A Basic <i>clap</i> Setup</h3>
<p>So, you can write a simple Rust program, and you kindda understand a <i>struct</i>. Good. Now lets add <i>clap</i> into the mix.</p>
<p>We first have to tell Cargo (the Rust "compiler" (sort of, but not really)) about Clap. This is done by adding some information to the "Cargo.toml" file. Before doing this, my Cargo.toml file for the "fun" program looks like this:</p>
<pre>
$ cat Cargo.toml
[package]
name = "fun"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
$
</pre>
<p>If you have a recent version of Clap/Cargo, you can add Clap to your "fun" project with this command:</p>
<code>$ cargo add clap --features derive</code>
<p>That'll generate some churn, after which your "Cargo.toml" file will look more like this (the new stuff is in hilite):</p>
<pre>
$ cat Cargo.toml
[package]
name = "fun"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]<hilite>
clap = { version = "4.2.7", features = ["derive"] }</hilite>
</pre>
<p>Now Cargo knows about clap. Your program does not, but Cargo does. When you compile your program again, Cargo will include all the clap stuff. Try it. Your program will run just as it did before, except for all the extra compiling of Clap.</p>
<p>We want to be able to specify our hammer and screwdriver on the command-line. The command will look something like this:</p>
<code>$ code run -- --name=Bob --hammer=peen --screwdriver=9</code>
<p>Add the <hilite>hi-lighted</hilite> code below, and delete the code that is in <s>Strikethru</s>. The "use" line simply tells our program the path to find parse-related stuff in the Clap crate. The "derive" line magically makes our struct definition able to associate its components with information coming from the Command-Line Argument Parser (clap).</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
<hilite>use clap::Parser;
#[derive(Parser)]</hilite>
struct ToolboxTemplate {
owner: String,
hammer: String,
screwdriver: i8,
}
fn main() {
<s>
// Let's build a custom toolbox for Him.
let his_box: ToolboxTemplate = ToolboxTemplate {
owner: "Joe".to_string(),
hammer: "sledge".to_string(),
screwdriver: 8,
};
// And one for Her.
let her_box: ToolboxTemplate = ToolboxTemplate {
owner: "Jane".to_string(),
hammer: "pink-handled".to_string(),
screwdriver: 4
};
println!("{} has a {} hammer and a Number {} screwdriver.",
his_box.owner, his_box.hammer, his_box.screwdriver
);
// Print the details of his toolbox.
println!("{} has a {} hammer and a Number {} screwdriver.",
his_box.owner, his_box.hammer, his_box.screwdriver
);
</s>
<hilite>
// Let's build a toolbox based on command-line arguments.
let toolbox: ToolboxTemplate = ToolboxTemplate::parse();
</hilite>
<s> // And of hers.</s>
<hilite> // And then print out the details of that toolbox.</hilite>
println!("{} has a {} hammer and a Number {} screwdriver.",
<s> her_box.owner, her_box.hammer, her_box.screwdriver</s>
<hilite> toolbox.owner, toolbox.hammer, toolbox.screwdriver</hilite>
);
} // end of main()
</pre>
</figure>
<p>If you try to run this with arguments, like so:</p>
<code>$ cargo run -- --name=Bob hammer=peen screwdriver=9</code>
<p>you'll get a message about an unexpected argument, and a tip that tells you to do what you're already doing, along with a "usage" blurb.</p>
<p>However, if you run your program this way:</p>
<code>$ cargo run -- --Bob peen 9</code>
<p>it works!</p>
<p>And that's all that's needed for a very basic <i>clap</i> setup. It's reading your command-line arguments according to the position they're given. If you change the order, like this:</p>
<code>$ cargo run -- --9 Bob peen</code>
<p>you'll break your program. Like this, though:</p>
<code>$ cargo run -- --peen Bob 9</code>
<p>and you'll just get unwanted results.</p>
<p>So we want to be able to name our arguments. That's done like this:</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
use clap::Parser;
#[derive(Parser)]
struct ToolboxTemplate {
<hilite> #[arg(long)]</hilite>
owner: String,
<hilite> #[arg(long)]</hilite>
hammer: String,
<hilite> #[arg(long)]</hilite>
screwdriver: i8,
};
...
</pre>
</figure>
<p>Try running that with <code>$ cargo run -- --name=Bob hammer=peen screwdriver=9</code>.</p>
<p>Hmm, a different error message. And again, it doesn't really make sense. Ah, but now I see it. We defined an argument named "owner", but we're typing in an argument named "name". Let's try this:</p>
<code>$ cargo run -- --<hilite>owner</hilite><s>name</s>=Bob hammer=peen screwdriver=9</code>.
<p>Yay! That works!</p>
<p>But what if we really want to type in "name" instead of "owner", but don't want to change the variable name? Easy. Just do this:</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
use clap::Parser;
#[derive(Parser)]
struct ToolboxTemplate {
#[arg(long<hilite>="name"</hilite>)]
owner: String,
#[arg(long)]
hammer: String,
#[arg(long)]
screwdriver: i8,
};
...
</pre>
</figure>
<p>What if we want to just use "s" for "screwdriver"?</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
use clap::Parser;
#[derive(Parser)]
struct ToolboxTemplate {
#[arg(long="name")]
owner: String,
#[arg(long)]
hammer: String,
#[arg(<s>long</s><hilite>short</hilite>)]
screwdriver: i8,
};
...
</pre>
</figure>
<p>Then our command-line would like look: <code>cargo run -- --name=Bob --hammer=peen -s=9 </code> Notice that single-letter arguments are introduced with a single-hyphen (e.g. <code>-s=9</code>), rather than a double-hyphen (<code>--s=9</code>).
<p>What if we want to just use "d" for "screw<em>D</em>river"?</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
use clap::Parser;
#[derive(Parser)]
struct ToolboxTemplate {
#[arg(long="name")]
owner: String,
#[arg(long)]
hammer: String,
#[arg(short<hilite>='d'</hilite>)]
screwdriver: i8,
};
...
</pre>
</figure>
<p>Notice that the definition uses single-quotes around "d" rather than double-quotes.</p>
<p>What if we want to allow either a short form or a long form?</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
use clap::Parser;
#[derive(Parser)]
struct ToolboxTemplate {
#[arg(<hilite>short,</hilite>long="name")]
owner: String,
<hilite> </hilite>
#[arg(<hilite>short,</hilite>long)]
hammer: String,
<hilite> </hilite>
#[arg(short='d',<hilite>long</hilite>)]
screwdriver: i8,
};
...
</pre>
</figure>
<p>What if we want to make the name optional, with a default?</p>
<figure>
<figcaption>src/main.rs</figcaption>
<pre>
use clap::Parser;
#[derive(Parser)]
struct ToolboxTemplate {
#[arg(short,long="name"<hilite>,default_value_t=String::from("Bubba")</hilite>)]
owner: String,
#[arg(short,long)]
hammer: String,
#[arg(short,long)]
screwdriver: i8,
};
...
</pre>
</figure>
<p>Note that <i>"Bubba".to_string()</i> won't work in this case, so we had to use an alternative method of converting a string literal to a <i>String</i>-type. Don't worry for now about understanding that; just know that <i>usually</i> it doesn't matter which conversion method you use, and that if one method doesn't work, try another.</p>
<p>This short tutorial won't answer all your questions, but it should get you started. Have fun, Rustacean!</p>
Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-66067907351861851402023-05-05T09:41:00.001-05:002023-05-05T09:41:42.058-05:00OCR On Your Smart Phone<p>Did you know that your phone can possibly do Optical Character Recognition (OCR)?</p>
<p>Mine, a Samsung Galaxy S22 Ultra, can.</p>
<ul style="text-align: left;"><li>I took a picture of an informative display hanging on the wall, that had two columns of side-by-side text.</li></ul>
<figure>
<figcaption>The image hanging on the wall.</figcaption>
<img alt="The image" height="240" src="https://scontent-dfw5-2.xx.fbcdn.net/v/t39.30808-6/345229212_1950363291991636_5680934085912396334_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_eui2=AeH-SuKCIP-R9x9IDRHpE_TEkHjYzkp0i5GQeNjOSnSLkW4SZu0Fsc7hBchR0I222os&_nc_ohc=-U5RWkQN03kAX8x0NXs&_nc_ht=scontent-dfw5-2.xx&oh=00_AfDha8KfQ70o4y3NNDcirQdfaLzZWLZl7Qtp7JkJ1X7TXQ&oe=64590B5D" title="The image" width="320" />
</figure>
<ul style="text-align: left;"><li>I then used the camera's photo editing software (the pen icon at the bottom of the image below) to crop the image to just one of the text columns, and saved (upper-right corner of the cropped image below) that image.</li></ul>
<figure>
<figcaption>The image in my phone's Gallery.</figcaption>
<img data-original-height="862" data-original-width="402" height="640" src="https://blogger.googleusercontent.com/img/a/AVvXsEgzY1R50YoNBzQ8mBXxCV9q1kSevZYOC1yLyo9BWwDwNhQkLZf3ysHfEYZRexqbEFLLV80Q3eo_3I8gdJ-D-SwECr8Mq4sbOgpbGgu3Mc5prXREMKaowdXpKevkjjF-3rskFpXbxQrURvEpsG0l52XTFbBNMP_xoUaUqGP_R7jc9BxMU5O7=w298-h640" title="The image as it looks in my phone's Gallery." width="298" />
</figure>
<figure>
<figcaption>Cropping the image to just one of the columns.</figcaption>
<img data-original-height="862" data-original-width="402" height="640" src="https://blogger.googleusercontent.com/img/a/AVvXsEgpAPxwnmmPUwdCLmR3C0gE9uunit-Bwe0MxYXneI6-IU3v4l9zVEYbPXNfBROud0BpPadAyGz_lFhaXbzyv25cVNza7Z_08UOMcITw5uLk9HniFjyby9hz1uiPlDFLn-aPf4UrGUrjgW4Hesq3M-S6ubXnKNAyyI4LRpEHAW8qVmGHiKOm=w299-h640" width="299" />
</figure>
<ul style="text-align: left;">
<li>Then at the bottom right corner of that same editing window is a little yellow "T" in a broken-outline box (see the Gallery image above). When I clicked on that "T", it OCR'd the text and highlighted it.</li>
<li>I was then able to single-press on the text, which popped up a menu allowing me to "Select All", which popped up another menu allowing me to "Copy".</li>
<li>I could then go to an editor of some sort, and "Paste" the text into the editor.</li>
<li>I then went back to my image, and edited it again, and "Revert"ed it back to the original.</li>
<li>I then repeated the process for the second column.</li>
</ul>
<p>In just a minute or two, I had the full text of the two columns of the informative display in an editor. With a clean original image with clean-looking text, the accuracy is very high.</p>
<figure style="border: 1px solid black;">
<figcaption style="border: 1px dotted red;">Finished text.</figcaption>
<p>The first unit of the hospital was erected in September 1924, at a cost of $150,000. West Texas Baptist Sanitarium had five stories, 72 rooms and admitted more than 800 patients during the first year.</p>
<p>When it opened, West Texas Baptist Sanitarium touted: hot and cold running water in each room; excellent nursing services; three modern elevators; three well-equipped operating rooms; capable physicians and surgeons; and an obstetrical department.</p>
<p>Labor and delivery services were quickly utilized. The first baby was born at Hendrick less than one month after the doors opened. Pauline Marie Turnidge, daughter of Mr. and Mrs. W.A. Turnidge, was born on October 17, 1924.</p>
<p>The vision of a hospital for the Texas Midwest was well under way as the vision of Reverend Millard Jenkins became a reality. The motto for West Texas Baptist Sanitarium was that it opened its doors to everyone, "no matter what your belief or creed."</p>
</figure>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-53595708124228686292023-02-03T18:15:00.034-06:002023-02-04T08:29:29.440-06:00Using Clap to Parse Rust Program Arguments<head>
<style>
pre{
box-shadow: 4px 4px 10px gray;
font-size: 70%;
}
code {
border: solid 1px black;
background-color: lightgray;
font: monospace;
font-size: small;
}
</style>
</head>
<p>
</p>The native Rust
argument-parsing capability is pretty limited, so
we are now turning to the third-party crate, "Clap" (which stands for <b>C</b>ommand-<b>L</b>ine <b>A</b>rgument <b>P</b>arser). To do that, let's
create a new project:
<pre>$ cd ~/projects/RUST
$ cargo new parse_clap
$ cd parse_clap</pre>
<p>You should pretty much know what the "src/main.rs" file
looks like in a new Cargo-created Rust project. Before we begin working
with that file, we need to let Cargo know we're going to use the "Clap"
crate. Since this is a new project, the "Cargo.toml" file has no
dependencies listed. We'll need to add a dependency to the "Cargo.toml" file for Clap.</p>
<p>Clap is found at "crates.io" (a Rust-maintained web site for all things
Rustacean). If you web-browse to that site, you can search for "clap", and you'll find (at least near
the time of this writing) both a version 3 and a version 4. We want the
most recent version, which at the time of this writing is 4.0.29. If
you'll click on it to get more details, you'll see in the right-hand
column a line that needs to be added to your "Cargo.toml" file,
specifically to the "[dependencies]" section, in order to tell Cargo how
to use the Clap crate. Up until very recent versions of Cargo, this had
to be added to the file manually, but with more recent versions of
Cargo, you can just run:</p>
<pre>$ cargo add clap@4.0.29</pre>
<p>If you don't want a specific version, but rather prefer the newest one available, you can instead run:</p><pre>$ cargo add clap</pre><p>Be aware, this may take a few minutes. If you do this, and then
examine your "Cargo.toml" file again, you'll discover that the needed line has
automatically been added to that file.</p>
<p>But, unfortunately, this instruction does not tell you that you
need to add more to that command, in order to
get all that we need. You can learn this by clicking on the "docs.rs"
link at the "crates.io" website. The command you really need (you can
run it even if you ran the previous command) is:</p>
<pre>$ cargo add clap --features derive</pre>
<p>The second run goes much faster than the first, because most of
the work has already been done in our first attempt to add this to
"Cargo.toml".</p>
<p>Now look again at your "Cargo.toml" file; if it looks pretty much like below, we should be good to go.</p>
<pre>[package]
name = "parse_clap"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0.29", features = ["derive"] }</pre>
<p>Now we're ready to edit your "src/main.rs" file to be identical to the following:</p>
<pre>use clap::Parser;
#[derive(Parser)]
struct ArgsType {
/// Are you happy or sad?
#[arg(short, long)]
mood: String,
}
fn main() {
let switches: ArgsType = ArgsType::parse();
if switches.mood == "happy"
{ println!("Whoo-hoo! I am {}! {}! {}! {}!", switches.mood, switches.mood, switches.mood, switches.mood) };
if switches.mood == "sad"
{ println!("Boo-hoo! I am {}!", switches.mood) };
<s>println!("Hello, world!");</s>
} // end of main()</pre>
<p>Now compile and run this with the indicated program arguments, like so:</p>
<pre>cargo run -- --mood happy</pre>
<p>Try providing different switches, in different order, in
different numbers. Try the "-V" option, as well as the "-v" non-option.
Try the "-h" option.</p>
<p>You can see that this Clap crate is already pretty useful, in
that it provides some help screens when the arguments are not what are
expected. It doesn't handle every wrong argument (as the program is
currently written), but you can see that there's some potential here.</p>
<p>Let's try to understand this program, and then explore a bit of that potential.</p>
<p>The first line, "use clap::Parser;" preps the system for the other two "Parse"-related statements. Just know it's needed.</p>
<p>The next line tells the system that the parser will be deriving its arguments from the <i>struct</i> we build next. Clap can be configured using the <i>Builder</i> Application Programming Interface (API) or the <i>Derive</i> API (or a mix, as I understand it). As a general rule, unless you need to get deeper under the hood of Clap, you'll probably want to use the Derive API. The FAQ at <a href="https://docs.rs/clap/4.1.4/clap/_faq/index.html#when-should-i-use-the-builder-vs-derive-apis">https://docs.rs/clap/4.1.4/clap/_faq/index.html#when-should-i-use-the-builder-vs-derive-apis</a> says this:</p>
<div style="border: 1px solid black;">
<h4 id="when-should-i-use-the-builder-vs-derive-apis"><a href="https://docs.rs/clap/4.1.4/clap/_faq/index.html#when-should-i-use-the-builder-vs-derive-apis"></a></h4>
<blockquote>
<h4 id="when-should-i-use-the-builder-vs-derive-apis"><a href="https://docs.rs/clap/4.1.4/clap/_faq/index.html#when-should-i-use-the-builder-vs-derive-apis">When should I use the builder vs derive APIs?</a></h4>
<p>Our default answer is to use the <a href="https://docs.rs/clap/4.1.4/clap/_derive/_tutorial/index.html" title="crate::_derive::_tutorial">Derive API</a>:</p>
<ul>
<li>Easier to read, write, and modify</li>
<li>Easier to keep the argument declaration and reading of argument in sync</li>
<li>Easier to reuse, e.g. <a href="https://crates.io/crates/clap-verbosity-flag">clap-verbosity-flag</a></li>
</ul>
<p>The <a href="https://docs.rs/clap/4.1.4/clap/_tutorial/index.html" title="crate::_tutorial">Builder API</a> is a lower-level API that someone might want to use for</p>
<ul>
<li>Faster compile times if you aren’t already using other procedural macros</li>
<li>More flexibility, e.g. you can look up an <a href="https://docs.rs/clap/4.1.4/clap/parser/struct.ArgMatches.html#method.get_many" title="crate::ArgMatches::get_many">arguments values</a>,
their <a href="https://docs.rs/clap/4.1.4/clap/parser/struct.ArgMatches.html#method.indices_of" title="crate::ArgMatches::indices_of">ordering with other arguments</a>, and <a href="https://docs.rs/clap/4.1.4/clap/parser/struct.ArgMatches.html#method.value_source" title="crate::ArgMatches::value_source">what set
them</a>. The Derive API can only report values and not
indices of or other data.</li>
</ul>
</blockquote>
</div>
<p>So in other words, do it the way we're doing here, with a struct, not the way other tutorials might show you, without a struct. At least until you want/need to dive deeper.</p>
<p>The "struct" section provides a defining template for a new <i>type</i> of variable. This section does
not declare an actual variable (we'll declare that later), but only a new type of variable. This new type of variable is based on a <i>struct</i> format. (A <i>struct</i> is a custom-made variable that holds other variables.)</p><p>Any variables declared to be of this new type are defined by this "struct' section, which defines what arguments are allowed to be given as the program's command-line arguments, and what the internal variable names are that will hold those arguments for use in the program. The Clap Derive API uses this <i>struct</i> type of structure to define and build this new type of variable. We could call this new type of variable anything we wanted, like "progInputs" or
"options" or "OptionsType", etc. We're calling it "ArgsType". Currently this new type of variable defines one internal variable, named
"mood", which is designated to hold String data.</p>
<p>The section that defines this inner "mood" variable is introduced by a line with three forward slashes (<i>///</i>).
Whereas two slashes are the beginning of a "comment", which is ignored
by the compiler but helps the programmer to keep notes about the code, a
three-slash line functions as both a comment and a documentation line,
which can be used by the compiler and by Clap to create help text. If
you run <code>cargo run -- --help</code>, you can see that text, "Are you happy or sad?", in the output.</p>
<p>The line beginning with a hash mark tells Clap how to handle this program argument: whether it can be entered as a long form (<i>--mood</i>), or as a short form (<i>-m</i>), or must it be required, or should it have a default value, etc.</p>
<p>We can add additional internal variables (and therefore additional program input possibilities) by adding more
"#[arg..." sections to the struct design. For example, in addition to
the user's mood, perhaps we'd like to know the person's name and age:</p>
<pre>struct ArgsType {
/// Are you happy or sad?
#[arg(short, long)]
mood: String,
/// What is your name?
#[arg(short, long)]
name: String,
/// What is your age?
#[arg(short, long, default_value_t = 16)]
age: u8,
} </pre>
<p>Notice that the "age" variable has a default value (which is a
bit silly, but this is just an example). Because of this, Clap won't
require the user to enter that option, but it will the other two. You
can force it to be required like this:</p>
<pre>#[arg(short, long, default_value_t = 3), required(true)]</pre>
<p>but that kind of defeats the purpose of having a default.</p>
<p>Although
technically an age entered on the command line in a command such as <code>cargo run -- --name Kent --age 35</code>
starts out as a "String" (everything entered on the command line starts
out as a "String"), by the time it gets to our "age" variable, Clap
will have converted it from a "String" to a "u8" (which is an unsigned (i.e., positive) integer in the range of 0 to 255).
</p><p>Note again that we have not yet declared a variable of this new type; we have only defined a new <i>type</i> of variable. We actually declare a variable in the <i>main()</i>
function. Note also that since the struct is defined outside of the
main() function, this definition of a new type of variable is "visible" (or "is in
scope") to all parts of the program within this "main.rs" file. If we
should create a new function later on in this same file, say, a function
called "part_two()", that function will be able to access this
"ArgsType" definition; had we put this definition within the main()
function, it would only be visible to the main() function itself, but
not to the "part_two()" function.</p>
<p>Now let's look at the main() function. The <code>let switches: ArgsType = ArgsType::parse();</code>
line actually defines our variable. The name of the variable is
"switches", and the type of the variable is, not String and not i32 and not u8 or
etc, but "ArgsType", the type we just invented. If this line seems complicated to you, take out the ": ArgsType", to make the line be just <code>let switches = ArgsType::parse();</code> which may be less daunting to look at and therefore less daunting to understand. It's basically just calling a "function" named "parse" that is "located" in the "ArgsType" struct we just built (not exactly, but close enough), and assigning the results of that "function" to the variable "switches".</p><p>The variable "switches" now holds three variables within it (assuming three options are given as program inputs), which we can access as "switches.mood", "switches.name", and "switches.age". Here are some mods to our program, including a boolean flag to specify if the user is human or not, which defaults to "no": </p>
<pre>use clap::Parser;
#[derive(Parser)]
struct ArgsType {
/// Are you happy or sad?
#[arg(short, long)]
mood: String,
/// What is your name?
#[arg(short, long, value_name = "What yo momma called you...")]
name: String,
/// What is your age?
#[arg(short, long, default_value_t = 16)]
age: u8,
/// Are you a human?
#[arg(short = 'H', long, default_value_t = false)] // 'h' would have conflicted with "help".
human: bool,
}
fn main() {
let switches: ArgsType = ArgsType::parse();
if switches.human {
println!("Hi, {}! You seem very {} to be {} years old, but that's understandable, since you are a human.",
switches.name,
switches.mood,
switches.age
);
} else {
println!("Hi, {}! You seem very {} to be {} years old, but that's understandable, since you are not a human.",
switches.name,
switches.mood,
switches.age
);
}
if switches.mood == "happy"
{ println!("Whoo-hoo! I am {}! {}! {}! {}!", switches.mood, switches.mood, switches.mood, switches.mood) };
if switches.mood == "sad"
{ println!("Boo-hoo! I am {}!", switches.mood) };
} // end of main()
</pre>
<p>Running this program results in:</p>
<pre>$ cargo run -- --mood happy --name Kent --age 253
Compiling parse_clap v0.1.0 (/home/westk/projects/RUST/parse_clap)
Finished dev [unoptimized + debuginfo] target(s) in 0.56s
Running `target/debug/parse_clap --mood happy --name Kent --age 253`
Hi, Kent! You seem very happy to be 253 years old.
Whoo-hoo! I am happy! happy! happy! happy!
$
$ cargo run -- --name=Kent --age 253 -m happy --human -h
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/parse_clap --name=Kent --age 253 -m happy --human -h`
Usage: parse_clap [OPTIONS] --mood <MOOD> --name <What yo momma called you...>
Options:
-m, --mood <MOOD> Are you happy or sad?
-n, --name <What yo momma called you...> What is your name?
-a, --age <AGE> What is your age? [default: 16]
-H, --human Are you a human?
-h, --help Print help information
-V, --version Print version information
$
</pre>
<p>Note also that various formats can be used for entering the arguments:</p>
<pre>
--name Kent
-nKent
-n=Kent
--name=Kent
<em>But </em>--nameKent<em> won't work.</em>
</pre>
<p>And that's pretty much it. We've got our feet wet with parsing arguments in Rust using the Clap crate.</p>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-15156743117756796812023-01-09T20:41:00.002-06:002023-01-09T20:43:20.926-06:00A Man's Most Important Relationships<p>The most important relationship a man has is with his God.</p><p>But the relationship he should focus most on is that with his wife.</p><p>God doesn't need a man's attention. A wife does.</p><p>Men, your wife likely has two fundamental needs that you need to meet:</p><ul style="text-align: left;"><li>the need to feel secure (finances seem to you like a huge part of this, but there are bigger security issues for her)</li><li>the need to feel valued (and listening to her and considering her viewpoint is a huge part of this)</li></ul><p></p><p>Focus on these things. Make her feel valued; make her feel safe.</p><p> </p><div style="text-align: left;"><span style="font-size: x-small;">Originally published at:</span></div><div style="text-align: left;"><span style="font-size: x-small;"><span><span class="w4txWc oJeWuf" id="c62" role="region"><span class="MUhG4e OGjyyf" data-blogurl="https://kentwest.blogspot.com/">https://kentwest.blogspot.com/2023/01/a-mans-most-important-relationships.html</span></span></span></span> </div>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-46945264062777559602022-10-31T14:44:00.014-05:002022-10-31T22:15:35.053-05:00Notes About PowerShell: Adding a TreeView to the GUI - Part 3<head>
<style type="text/css">
p { line-height: 115%; margin-bottom: 0.1in; background: transparent }
p.coding-western { font-family: "Bitstream Vera Sans Mono", monospace; font-size: 8pt; line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-cjk { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-ctl { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
a:link { color: #000080; so-language: zxx; text-decoration: underline }
pre {
padding: 10px;
border: 1px solid black;
margin: 0;
background-color: Gainsboro;
font-size: smaller;
}
</style>
</head>
<body dir="ltr" lang="en-US" link="#000080" vlink="#800000">
<p> In <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview_28.html">Notes About PowerShell: Adding a TreeView to the GUI - Part 2</a> of this series, we had a complete set of four PowerShell scripts that together creates a GUI window that displays a Windows Forms TreeView, allowing the user to select a family member from a small family tree. In this post, we're going to replace that family tree with the computer's file system.</p>
<p>Let's make one quick modification to <strong>tinker.ps1</strong> file so that our results are graphically displayed in addition to textually in the console. Add in the bolded code below:</p>
<pre>
...
If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The data retrieved are:")
Write-Host("`t Node: `"$($Node.Text)`".")
Write-Host("`tPath to the Node: `"$($NodePath.Text)`".")
<strong> [System.Windows.Forms.MessageBox]::Show("Results:`n`nNODE:`n `"$($Node.Text)`"`n`nPATH TO NODE:`n `"$($NodePath.Text)`"")</strong>
...</pre>
<p>Give 'er a test spin.</p>
<p>Okay, on to putting the filesystem into a treeview. First, we'll need to have the drive letters of Windows. For experimentation/learning purposes, at a PowerShell prompt (not in your script), enter the following command, and you'll see results similar to the following:</p>
<pre>PS C:\Users\acutech> Get-PSDrive
Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
Alias Alias
C 24.30 55.00 FileSystem C:\ Users\acutech
Cert Certificate \
D FileSystem D:\
Env Environment
Function Function
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE
Variable Variable
WSMan WSMan
PS C:\Users\acutech> </pre>
<p>We're only interested in the filesystem drive letters, not the registry keys or certificates or etc. So let's put some limitations on the command:</p>
<pre>PS C:\Users\acutech> Get-PSDrive -PSProvider FileSystem
Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
C 24.30 55.00 FileSystem C:\ Users\acutech
D FileSystem D:\
PS C:\Users\acutech></pre>
<p>Better. But all we really want is the drive letter itself:</p>
<pre>PS C:\Users\acutech> (Get-PSDrive -PSProvider FileSystem).Root
C:\
D:\
PS C:\Users\acutech> (Get-PSDrive -PSProvider FileSystem).Name
C
D</pre>
<p>Great! Both of these commands give us an array containing the filesystem drives. Let's load them up into the treeview.</p>
<p>Let's start with a reminder of our <strong>tinker_add_nodes.ps1</strong> file:
<pre>
$TreeView.Nodes.Add("John")
$TreeView.Nodes[0].Nodes.Add("Mary")
$TreeView.Nodes[0].Nodes.Add("Fred")
$TreeView.Nodes[0].Nodes[1].Nodes.Add("Delbert")
$TreeView.Nodes[0].Nodes.Add("Alvin")
$TreeView.Nodes.Add("William")
$TreeView.Nodes[1].Nodes.Add("Estelle")
$TreeView.Nodes[1].Nodes.Add("Angus")
$TreeView.Nodes[1].Nodes.Add("Eugene")
$TreeView.Nodes[1].Nodes.Add("Marvin")
$TreeView.SelectedNode = $TreeView.Nodes[0].Nodes[1].Nodes[0]
$Win.ActiveControl = $TreeView
$Node.Text = $TreeView.SelectedNode.Text</pre>
<p>Delete all of that code; we're done with it. And replace it with this code:</p>
<pre>
$Drives = (Get-PSDrive -PSProvider FileSystem).Root # Get the drive letters in an array named $Drives
foreach ($_ in $Drives) { # For each drive letter in the array,
$TreeView.Nodes.Add($_) # add the drive letter to the treeview.
}</pre>
<p> Open up <strong>.tinker.ps1</strong> if you haven't already done so, and run it. You should get something like this:</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLnlWBwz9NPP-LeQcmK43I2AK-eqkhtOo4pHYEdfR__Tp0WgCFtRwJxmxg_hIfT-qE-5qHwCH_Acr2AirA1sodcemvs07MxEtLtGsvhfs768Y8RweRbjGtIdkj0aJn9NE6UUKFLOSiAx2EcwS7MpCCBg071JTBjbEA2dc7esh5oAS3NnNV/s1273/Drives.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="1033" data-original-width="1273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLnlWBwz9NPP-LeQcmK43I2AK-eqkhtOo4pHYEdfR__Tp0WgCFtRwJxmxg_hIfT-qE-5qHwCH_Acr2AirA1sodcemvs07MxEtLtGsvhfs768Y8RweRbjGtIdkj0aJn9NE6UUKFLOSiAx2EcwS7MpCCBg071JTBjbEA2dc7esh5oAS3NnNV/s320/Drives.png"/></a></div>
<h2>This Document is Under Construction</h2>
</body>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-38495561848530409312022-10-28T13:16:00.093-05:002022-10-31T20:40:48.825-05:00Notes About PowerShell: Adding a TreeView to the GUI - Part 2<head>
<style type="text/css">
p { line-height: 115%; margin-bottom: 0.1in; background: transparent }
p.coding-western { font-family: "Bitstream Vera Sans Mono", monospace; font-size: 8pt; line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-cjk { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-ctl { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
a:link { color: #000080; so-language: zxx; text-decoration: underline }
pre {
padding: 10px;
border: 1px solid black;
margin: 0;
background-color: Gainsboro;
font-size: smaller;
}
</style>
</head>
<body dir="ltr" lang="en-US" link="#000080" vlink="#800000">
<p>See <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview.html">Part 1 here</a>.</p>
<p>If you just want to see the code, without reading how we get there, the <a href="#listings">code listings are at the bottom of this page</a>.</p>
<p>In <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview.html">Part 1</a>, we built a basic Windows form with a blank treeview. Now we're going to populate the treeview. Probably the three most popular "databases" that are looked at in PowerShell in a tree form are the file system, the Windows Registry, and Active Directory domains. I plan to look at each of these three sources via a treeview, but let's start with something simpler, a simple family tree.</p>
<!--
<p>Ideally we'd have a robust script that doesn't make too many assumptions, but for the sake of simplicity, our script here won't be too wary of possible problems, and will be making assumptions, such as that the main drive we'll be looking at is Drive C:. We're mostly just trying to establish basic understandings of the workings.</p>
-->
<p>As you remember from <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview.html">Part 1</a>, most of our GUI-window construction takes place in an external file, "<strong>tinker_GUI.ps1</strong>", which is <em>dot-sourced</em> from our primary script file, "<strong>tinker.ps1</strong>". That primary script file currently looks like this:</p>
<pre>
# Offload GUI-building into a different file to decrease clutter here.
. "$PSScriptRoot\tinker_GUI.ps1"
# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The name in the box is `"$($NameBox.Text)`".")
} elseif ($Win.DialogResult -eq "Cancel") {
Write-Host("The X was pressed. I`'m not going to tell you the name that is in the box.")
} # end of If
</pre>
<p>If you've followed along with <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview.html">Part 1</a>, you should be able to run the above script, and see a window with an empty treeview, a box for entering your name, and an OK button.</p>
<p>Since a treeview looks at data that is in a tree-like form, we're probably less interested in the user's name and more interested in the leaf-node endpoint of interest in our tree, and the path it takes to get to that leaf-node. So let's edit the second, external script file, so that it replaces the "Name" box with a "Node" box. Let's also take out the pre-fill of that box, and the activation/selection/focus of that box.</p>
<p>Use your <em>PowerShell ISE</em> to open the "<strong>tinker_GUI.ps1</strong>" script file, and make the following modifications, deleting the <strike>strike-out</strike> text and adding in the <strong>bolded</strong> text. You'll find it easier to change "NameBox" to "Node" by using the <em>PowerShell ISE</em>'s <em>Edit</em> / <em>Replace in Script...</em> feature to replace all instances of "NameBox" with "Node".</p>
<pre>
...
$Win.AcceptButton = $buttonOK # Click = "Close form; I accept it as it now is."
# <strike>A textbox.</strike>The node.
$<strike>NameBox</strike><strong>Node</strong> = New-Object "System.Windows.Forms.Textbox"
$<strike>NameBox</strike><strong>Node</strong>.Size = "175,25"
$<strike>NameBox</strike><strong>Node</strong>.Location = "290,40"
$<strike>NameBox.Text = "Babushka" # Pre-fill the box.</strike>
$Win.Controls.Add($<strike>NameBox</strike><strong>Node</strong>)
<strike>$Win.ActiveControl = $NameBox # Once form shows, select this box.</strike>
# Put a label with the box.
$Label_<strike>NameBox</strike><strong>Node</strong> = New-Object 'System.Windows.Forms.Label'
$Label_<strike>NameBox</strike><strong>Node</strong>.Text = "<strike>Enter your name</strike>Selected Node:"
$Label_<strike>NameBox</strike><strong>Node</strong>.Size = "150, 25"
$Label_<strike>NameBox</strike><strong>Node</strong>.Location = '290, 20'
$Win.Controls.Add($Label_<strike>NameBox</strike><strong>Node</strong>)
...</pre>
<p>Try running your script, to make sure it all works as expected.</p>
<p>We're also interested in the path to that node, so let's add a "Path to Node" box (by adding the <strong>bolded</strong> text in the indicated place).</p>
<pre>
...
$Label_Node.Location = '290, 20'
$Win.Controls.Add($Label_Node)
<strong>
# Path to the node.
$NodePath = New-Object "System.Windows.Forms.Textbox"
$NodePath.Size = "175,25"
$NodePath.Location = "290,120"
$Win.Controls.Add($NodePath)
# Put a label with the box.
$Label_NodePath = New-Object 'System.Windows.Forms.Label'
$Label_NodePath.Text = "Path to Node:"
$Label_NodePath.Size = "150, 25"
$Label_NodePath.Location = '290, 100'
$Win.Controls.Add($Label_NodePath)</strong>
# The TreeView object.
$TreeView = New-Object System.Windows.Forms.TreeView
...</pre>
<p>Now, for our family tree.</p>
<p>Imagine two brothers, John and William. John has three kids, Mary, Fred, and Alvin, and Fred has one, Delbert. William has four, Estelle, Angus, Eugene, and Marvin. Let's make a tree that views these relationships, and put that tree into the treeview object on our GUI form.</p>
<p>Ideally, since adding things to the treeview is conceptually different than actually building the window and the node and path and treeview box forms, we'd put the coding instructions to add things to the treeview somewhere else besides in this file. However, once the form is shown on-screen (with the <em>$Win.ShowDialog()</em>, no code below that line is executed until that window form is closed.</p>
<p>So, our options are to embed the code in the midst of all this other code, or to put the code to an external file and then import it as a dot-sourced file into a location just above the <em>$Win.ShowDialog()</em> line, or to put the code into a function and call that function just prior to the <em>$Win.ShowDialog()</em> line.</p>
<p>Just for sake of example, let's add a couple of nodes, the two patriarch brothers, with the code embedded in the <strong>tinker_GUI.ps1</strong> file:</p>
<pre>
...
$Win.Controls.Add($Label_TreeView) # Add the label to the form.
<strong>
$TreeView.Nodes.Add("John")
$TreeView.nodes.Add("William")
</strong>
# Display the form
$Win.ShowDialog()
...</pre>
<p>Run the script, and you should start seeing the treeview come together.</p>
<p>Now, instead of embedding this code in the <strong>tinker_GUI.ps1</strong> file, let's move that code out of it, and into a new file we can name <strong>tinker_add_nodes.ps1</strong>. So <strong>tinker_GUI.ps1</strong> becomes:</p>
<pre>
...
$Win.Controls.Add($Label_TreeView) # Add the label to the form.
<strike>
$TreeView.Nodes.Add("John")
$TreeView.nodes.Add("William")</strike>
<strong>
. "$PSScriptRoot\tinker_add_nodes.ps1" # Import file with code to populate tree.
</strong>
# Display the form
$Win.ShowDialog()
...</pre>
<p>and <strong>tinker_add_nodes.ps1</strong> becomes:</p>
<pre>
$TreeView.Nodes.Add("John")
$TreeView.nodes.Add("William")
</pre>
<p>Running <strong>tinker.ps1</strong> should still produce the output you expect.</p>
<p>Now let's add the kids and grandkid.</p>
<pre>
$TreeView.Nodes.Add("John")
<strong>$TreeView.Nodes[0].Nodes.Add("Mary")
$TreeView.Nodes[0].Nodes.Add("Fred")
$TreeView.Nodes[0].Nodes[1].Nodes.Add("Delbert")
$TreeView.Nodes[0].Nodes.Add("Alvin")
</strong>$TreeView.Nodes.Add("William")
<strong>$TreeView.Nodes[1].Nodes.Add("Estelle")
$TreeView.Nodes[1].Nodes.Add("Angus")
$TreeView.Nodes[1].Nodes.Add("Eugene")
$TreeView.Nodes[1].Nodes.Add("Marvin")</strong>
</pre>
<p>As you can see, the .Nodes values are arrays, that attach behind each other like the railroad cars of a train. The first level array, <em>$Win.Nodes</em> has two elements: <em>.Nodes[0]</em> contains the name "John". So by adding "Mary" and "Fred" as new element nodes to <em>$Win.Node[0]</em>, "Mary" becomes <em>$Win.Nodes[0].Nodes[0]</em> and shows up in the treeview as "John's" daughter, as does "Fred" as <em>$Win.Nodes[0].Nodes[1]</em>. Don't worry too much if this doesn't yet make sense to you; it should start making more sense the farther we go and the more you work with it.</p>
<p>To help visualize this, we can add this code:</p>
<pre> ...
$TreeView.Nodes[1].Nodes.Add("Eugene")
$TreeView.Nodes[1].Nodes.Add("Marvin")
<strong>$TreeView.SelectedNode = $TreeView.Nodes[0].Nodes[1].Nodes[0]</strong>
...</pre>
<p>You'll see that the tree has been expanded out to Delbert, without you having to expand anything using your mouse. If you press TAB a couple of times, until the focus lands on the treeview box, you'll see "Delbert" is highlighted.</p>
<p>We can even move the TAB focus to the treeview programatically:</p>
<pre> ...
$TreeView.Nodes[1].Nodes.Add("Eugene")
$TreeView.Nodes[1].Nodes.Add("Marvin")
$TreeView.SelectedNode = $TreeView.Nodes[0].Nodes[1].Nodes[0]
<strong>$Win.ActiveControl = $TreeView</strong>
...</pre>
<p>You may recall we used the "$Win.ActiveControl" setting earlier, in the definitions for the "Node" box, but then deleted that. If it had not been deleted, this second instance would simpy overwrite the effects of the first instance, because it comes later in the code.</p>
<p>But speaking of the "Node" box, this'd be a great time to fill it with the selected node:</p>
<pre>...
$TreeView.SelectedNode = $TreeView.Nodes[0].Nodes[1].Nodes[0]
$Win.ActiveControl = $TreeView
<strong>$Node.Text = $TreeView.SelectedNode.Text</strong></pre>
<p>You might think that since this is a property of <em>$Node</em>, it should go with all the other property settings in the section of code where we first defined <em>$Node</em>. That's great thinking. Unfortunately, since PowerShell is an interpreted language rather than a compiled language, PowerShell doesn't know about the <em>$TreeView.SelectedNode.Text</em> until after it reads this section of code, long after it has passed that section of code. So this assignment needs to go here instead of there.</p>
<p>This would also be a good time to report the selected node after the OK button is pressed. Remember, this code is in the main file, <strong>tinker.ps1</strong>.</p>
<pre>
...
# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The <strike>name</strike><strong>node</strong> in the box is `"$($<strike>NameBox</strike><strong>Node</strong>.Text)`".")
} elseif If ($Win.DialogResult -eq "Cancel") {
Write-Host("The X was pressed. I`'m not going to tell you the <strike>name</strike><strong>node</strong> that is in the box.")
} # end of If
</pre>
<p>If you click around in the treeview, say, by clicking on "Mary", you'll see the focus follows your click. But nothing else happens.</p>
<p>In order to perform some action when we click in the treeview, we'll need more code. In the definition for the treeview, we'll add this line:</p>
<pre> ...
$TreeView.Size = "240,400"
<strong>$treeview.add_NodeMouseClick({Write-Host("Ouch")})</strong>
$Win.Controls.Add($TreeView)
...</pre>
<p>All this does is print "Ouch" to the PowerShell console. (You could print it to a pop-up message box with a slightly more-complicated statement - <em>[System.Windows.Forms.MessageBox]::Show("Ouch")</em>. (The brackets are kind of a short-hand way of creating an object on-the-fly, without the whole "$MessageBox = New-Object..." variable declaration thingy, but since this is a run-time "declaration", the dot, ".", becomes "::" (among other differences).))</p>
<p>Each time you click anywhere in the treeview area, you'll get an "Ouch".</p>
<p>However, what if we want to do more than a single command on a mouse-click?</p>
<p>We could continue adding code in this embedded manner, like so:</p>
<pre>
$treeview.add_NodeMouseClick(
{
Write-Out("Ouch")})
Write-Host("You just clicked away from $($TreeView.SelectedNode).") # Need to fix: click on the same already-highlighted name, and this message will be inaccurate.
Write-Host("Stop it! That hurts!")
}
)
</pre>
<p>Or we could use a variable here that functions like a function by standing in for a whole section of code, or use an actual function call. To keep the various code pieces separate as much as we can, and keep one section of code uncluttered by another portion of code, let's put the variable/function in another file, say, <strong>tinker_node_selected.ps1</strong>.</p>
<pre>
$TreeViewMouseClickEvent = {
Write-Host("Ouch")
Write-Host("You just clicked away from $($TreeView.SelectedNode).") # Need to fix: click on the same already-highlighted name, and this message will be inaccurate.
Write-Host("Stop it! That hurts!")
}
</pre>
<p>And we'll have to make two changes to <strong>tinker_GUI.ps1</strong>:</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
<strong>. "$PSScriptRoot\tinker_node_selected.ps1"</strong>
...
<strike>$treeview.add_NodeMouseClick({Write-Host("Ouch")})</strike>
<strong>$treeview.add_NodeMouseClick($TreeViewMouseClickEvent)</strong>
</pre>
<p>Or we can create a function, and call that function:</p>
<pre>
function TreeViewMouseClickEvent {
Write-Host("Ouch")
Write-Host("You just clicked away from $($TreeView.SelectedNode).") # Need to fix: click on the same already-highlighted name, and this message will be inaccurate.
Write-Host("Stop it! That hurts!")
} # end of TreeViewMouseClickEvent function
</pre>
and
<pre>
$treeview.add_NodeMouseClick({TreeViewMouseClickEvent})
</pre>
<p>Notice that with the embedded and function methods, curly braces are needed, but not with the variable method. Notice also that the function method is not preceded by the $ sign, whereas the variable method is. Lastly, note that the variable declaration includes an = sign, whereas the function declaration does not.</p>
<p>For a fuller look at these three methods, see <a href="https://social.technet.microsoft.com/wiki/contents/articles/25911.how-to-add-a-powershell-gui-event-handler-part-1.aspx">here</a>. We'll continue on using the function method.</p>
<p>If you watch the console as you click around in the treeview, you'll notice that the name that appears in the console is the name you leave, rather than the name on which you click. That's not the behavior we want. The reason this happens is because the PowerShell scripting engine runs the function/variable/embedded code <em>before</em> changing the treeview's selected node. We don't want to process the name we just left, but rather the name we just clicked on. So we'll have to change our function, to use arguments that the <em>Add_NodeMouseClick</em> feature automatically provides to us:</p>
<pre>
...
<strike>Write-Host("You just clicked away from $($TreeView.SelectedNode).") # Need to fix: click on the same already-highlighted name, and this message will be inaccurate.</strike>
<strong>Write-Host("You just clicked on $($_.Node.Text).")</strong>
...
</pre>
<p>The <em>$_</em> represents the un-named variable given to our function which holds the arguments from the <em>Add_NodeMouseClick</em> event.</p>
<p>Those console messages aren't very valuable to us, though. Let's instead use this function to change out the text of the two text boxes on our form.</p>
<pre>
function TreeViewMouseClickEvent {
<strike> Write-Host("Ouch")
Write-Host("You just clicked on $($_.Node.Text).")
Write-Host("Stop it! That hurts!")</strike>
<strong># Fill in the "Node" and Path: fields on the form, based on the node just selected.
$Node.Text = $_.Node.Text
$NodePath.Text = $_.Node.FullPath</strong>
} # end of TreeViewMouseClickEvent function
</pre>
<p>This almost works like we want, except you'll notice that if you click on a plus or minus in the treeview, the "Node:" and "Path:" fields change, even if we haven't actually selected a different node. Thinking about it, that makes sense; the Mouse-click event is raised when we click the mouse in the treeview area. We just need to use a different event. Easy. We'll also change the name of our function, to make it more accurate as to what is going on.</p>
<p>In <strong>tinker_node_selected.ps1</strong>:</p>
<pre>
...
<strike>function TreeViewMouseClickEvent {</strike>
<strong>function TreeNodeSelected {</strong>
...
<strike>} # end of TreeViewMouseClickEvent function</strike>
<strong>} # end of TreeNodeSelected function</strong>
</pre>
<p>And in <strong>tinker_GUI.ps1</strong>:</p>
<pre>
...
<strike>$treeview.add_NodeMouseClick({TreeViewMouseClickEvent})</strike>
<strong>$treeview.add_AfterSelect({TreeNodeSelected})</strong>
...
</pre>
<p>Now we have a working form with a working treeview that allows us to read the selected node and its path. Let's change our output to include both of these bits of data. In <strong>tinker.ps1</strong>:</p>
<pre>
...
<strike>If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The node in the box is `"$($Node.Text)`".")
</strike>
<strong>If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The data retrieved are:")
Write-Host("`t Node: `"$($Node.Text)`".")
Write-Host("`tPath to the Node: `"$($NodePath.Text)`".")
...
</strong></pre>
<p id="listings">Just for clarity, here are the four files we are using, in their entirety:</p>
<p><strong>tinker.ps1</strong>:<p>
<pre>
# Offload GUI-building into a different file to decrease clutter here.
. "$PSScriptRoot\tinker_GUI.ps1"
# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The data retrieved are:")
Write-Host("`t Node: `"$($Node.Text)`".")
Write-Host("`tPath to the Node: `"$($NodePath.Text)`".")
} elseif ($Win.DialogResult -eq "Cancel") {
Write-Host("The X was pressed. I`'m not going to tell you the node that is in the box.")
} # end of If</pre>
<p><strong>tinker_GUI.ps1</strong>:<p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
. "$PSScriptRoot\tinker_node_selected.ps1"
# Create a new window form.
$Win = New-Object System.Windows.Forms.Form
$Win.ClientSize = '490,460' # Size of window frame.
$Win.text = "My Parent Window" # Title of the Win frame.
$Win.BackColor = "#ffffff" # Background color of the Win frame.
# Create an OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button' # Create the button.
$buttonOK.Text = "OK" # Puts text on the button.
$buttonOK.Location = '350, 240'
$buttonOK.Size = '60, 25'
$buttonOK.Anchor = 'Bottom, Right' # Keep button in relative bottom-right of Win.
$buttonOK.DialogResult = "OK" # "None|OK|Cancel|Abort|Retry|Ignore|Yes|No".
$Win.Controls.Add($buttonOK) # Add the button to the form.
$Win.AcceptButton = $buttonOK # Click = "Close form; I accept it as it now is."
# The node.
$Node = New-Object "System.Windows.Forms.Textbox"
$Node.Size = "175,25"
$Node.Location = "290,40"
$Win.Controls.Add($Node)
# Put a label with the box.
$Label_Node = New-Object 'System.Windows.Forms.Label'
$Label_Node.Text = "Selected Node:"
$Label_Node.Size = "150, 25"
$Label_Node.Location = '290, 20'
$Win.Controls.Add($Label_Node)
# Path to the node.
$NodePath = New-Object "System.Windows.Forms.Textbox"
$NodePath.Size = "175,25"
$NodePath.Location = "290,120"
$Win.Controls.Add($NodePath)
# Put a label with the box.
$Label_NodePath = New-Object 'System.Windows.Forms.Label'
$Label_NodePath.Text = "Path to Node:"
$Label_NodePath.Size = "150, 25"
$Label_NodePath.Location = '290, 100'
$Win.Controls.Add($Label_NodePath)
# The TreeView object.
$TreeView = New-Object System.Windows.Forms.TreeView
$TreeView.Location = "10,40"
$TreeView.Size = "250,400"
$treeview.add_AfterSelect({TreeNodeSelected})
$Win.Controls.Add($TreeView) # Add the tree view to the main window form.
# Put a label with the object.
$Label_TreeView = New-Object System.Windows.Forms.Label
$Label_TreeView.Location = "10,20"
$Label_TreeView.Size = "150,25"
$Label_TreeView.Text = "My Tree Object:"
$Win.Controls.Add($Label_TreeView) # Add the label to the form.
. "$PSScriptRoot\tinker_add_nodes.ps1" # Import file with code to populate tree.
# Display the form.
$Win.ShowDialog() </pre>
<p><strong>tinker_add_nodes.ps1</strong>:<p>
<pre>$TreeView.Nodes.Add("John")
$TreeView.Nodes[0].Nodes.Add("Mary")
$TreeView.Nodes[0].Nodes.Add("Fred")
$TreeView.Nodes[0].Nodes[1].Nodes.Add("Delbert")
$TreeView.Nodes[0].Nodes.Add("Alvin")
$TreeView.Nodes.Add("William")
$TreeView.Nodes[1].Nodes.Add("Estelle")
$TreeView.Nodes[1].Nodes.Add("Angus")
$TreeView.Nodes[1].Nodes.Add("Eugene")
$TreeView.Nodes[1].Nodes.Add("Marvin")
$TreeView.SelectedNode = $TreeView.Nodes[0].Nodes[1].Nodes[0]
$Win.ActiveControl = $TreeView
$Node.Text = $TreeView.SelectedNode.Text</pre>
<p><strong>tinker_node_selected.ps1</strong>:<p>
<pre>function TreeNodeSelected {
# Fill in the "Node" and Path: fields on the form, based on the node just selected.
$Node.Text = $_.Node.Text
$NodePath.Text = $_.Node.FullPath
} # end of TreeViewMouseClickEvent function</pre>
<p>Next up, in <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview_31.html">Part 3</a>, we'll do this using the file system.</p>
</body>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-87286485640522583842022-10-28T08:19:00.071-05:002022-10-31T12:17:48.292-05:00Notes About PowerShell: Adding a TreeView to the GUI, Part 1
<head>
<style type="text/css">
p { line-height: 115%; margin-bottom: 0.1in; background: transparent }
p.coding-western { font-family: "Bitstream Vera Sans Mono", monospace; font-size: 8pt; line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-cjk { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-ctl { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
a:link { color: #000080; so-language: zxx; text-decoration: underline }
pre {
padding: 10px;
border: 1px solid black;
margin: 0;
background-color: Gainsboro;
font-size: smaller;
}
</style>
</head>
<body dir="ltr" lang="en-US" link="#000080" vlink="#800000"><p>Previously we built <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-simplest-gui.html">the Simplest PowerShell GUI</a>, and then added a few features to it <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-more-complex-gui.html">here</a>.</p>
<p>The script below is that last more complex GUI (with a few edits). It presents a window frame, with an OK button object, and a textbox object that asks for a name to be entered. We'll build on this script to add a treeview object. A treeview allows you to browse a tree-like structure, such as the file system, or an Active Directory domain, or the Windows Registry. So fire up the ol' <em>PowerShell ISE</em>, copy and paste in the following code (making sure the quotation marks don't turn into "smart" quotation marks), and then save the file as something like "<strong>tinker.ps1</strong>". Then try running it to make sure it works.</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form.
$Win = New-Object System.Windows.Forms.Form
$Win.ClientSize = '490,460' # Size of window frame.
$Win.text = "My Parent Window" # Title of the Win frame.
$Win.BackColor = "#ffffff" # Background color of the Win frame.
# Create an OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button' # Create the button.
$buttonOK.Text = "OK" # Puts text on the button.
$buttonOK.Location = '350, 240'
$buttonOK.Size = '60, 25'
$buttonOK.Anchor = 'Bottom, Right' # Keep button in relative bottom-right of Win.
$buttonOK.DialogResult = "OK" # "None|OK|Cancel|Abort|Retry|Ignore|Yes|No".
$Win.Controls.Add($buttonOK) # Add the button to the form.
$Win.AcceptButton = $buttonOK # Click = "Close form; I accept it as it now is."
# A textbox.
$NameBox = New-Object "System.Windows.Forms.Textbox"
$NameBox.Size = "175,25"
$NameBox.Location = "290,40"
$NameBox.Text = "Babushka" # Pre-fill the box.
$Win.Controls.Add($NameBox)
$Win.ActiveControl = $NameBox # Once form shows, select this box.
# Put a label with the box.
$Label_NameBox = New-Object 'System.Windows.Forms.Label'
$Label_NameBox.Text = "Enter your name:"
$Label_NameBox.Size = "150, 25"
$Label_NameBox.Location = '290, 20'
$Win.Controls.Add($Label_NameBox)
# Display the form.
$Win.ShowDialog()
# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The name in the box is `"$($NameBox.Text)`".")
} elseif ($Win.DialogResult -eq "Cancel") {
Write-Host("The X was pressed. I`'m not going to tell you the name that is in the box.")
} # end of If
</pre>
<p>Now we're ready to create the empty treeview object. The text in <strong>bold</strong> below is what we're adding.</p>
<pre> ...
# Put a label with the box.
$Label_NameBox = New-Object 'System.Windows.Forms.Label'
$Label_NameBox.Text = "Enter your name:"
$Label_NameBox.Size = "175, 23"
$Label_NameBox.Location = '10, 20'
$Win.Controls.Add($Label_NameBox)
<strong># The TreeView object.
$TreeView = New-Object System.Windows.Forms.TreeView
$TreeView.Location = "10,40"
$TreeView.Size = "250,400"
$Win.Controls.Add($TreeView) # Add the tree view to the main window form.
# Put a label with the object.
$Label_TreeView = New-Object System.Windows.Forms.Label
$Label_TreeView.Location = "10,20"
$Label_TreeView.Size = "150,25"
$Label_TreeView.Text = "My Tree Object:"
$Win.Controls.Add($Label_TreeView) # Add the label to the form.</strong>
# Display the form.
$Win.ShowDialog()
...
</pre>
<p>That's a lot of code to be scrolling up and down in, and it's pretty easy to get lost in it. It'd be good if we could break up this big monolithic piece of code into smaller, more manageable chunks.</p>
<p>Most of this code is involved in the actual GUI-building, so the breaking up into chunks won't do a great deal for us at this point, but it will help to compartmentalize the different functional parts. We'll break this code into two pieces, and put them in separate files. On the one hand, that's a disadvantage, because now you're dealing with multiple files for your project, but on the other hand, it's an advantage, in that it breaks up the project into more manageable bite-size pieces.</p>
<p>So click on <em>Edit</em> / <em>Select All</em> in your <em>PowerShell ISE</em>, then <em>Edit</em> / <em>Copy</em>, to copy all the text into your Clipboard. Now click on click on <em>File</em> / <em>New</em> to open up a new blank tab, and then <em>Edit</em> / <em>Paste</em> to paste the script into a new tab. Go to the very bottom of the file and select everything after the "$Win.ShowDialog()" line, and then <em>Edit</em> / <em>Cut</em> that selection into the Clipboard. Now save this tab as something like "<strong>tinker_GUI.ps1</strong>".<p>
<p>You now have two almost identical files, one named "<strong>tinker.ps1</strong>" that has the entire script, and one named "<strong>tinker_GUI.ps1</strong>" that has everything except the last few lines.</p>
<p>Now go back to the "<strong>tinker.ps1</strong>" tab and with all the text still selected, <em>Edit</em> / <em>Paste</em>.</p>
<p>Now you have transferred all the GUI-building pieces of the script to an external file named "<strong>tinker_GUI.ps1</strong>", and left just a little bit of code in the original file, "<strong>tinker.ps1</strong>".</p>
<p>But the original "<strong>tinker.ps1</strong>" no longer knows anything about the code in the external "<strong>tinker_GUI.ps1</strong>" file, so we'll have to tell it about the external file. Here now is what the "<strong>tinker.ps1</strong>" file should look like, with code in <strong>bold</strong> that you need to add:</p>
<pre><strong>
# Offload GUI-building into a different file to decrease clutter here.
. "$PSScriptRoot\tinker_GUI.ps1"</strong>
# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
Write-Host("The OK button was pressed. The name in the box is `"$($NameBox.Text)`".")
} elseif ($Win.DialogResult -eq "Cancel") {
Write-Host("The X was pressed. I`'m not going to tell you the name that is in the box.")
} # end of If
</pre>
<p>Save the file, and try running it. Note that the "<strong>tinker.ps1</strong>" tab must be the active tab to run the program. If the active tab is <strong>tinker_GUI.ps1</strong>, that portion of code will run, but the <strong>tinker.ps1</strong> portion of code won't run. In this case, it really doesn't matter all that much. That won't always be the case, though; you need to run the tab that contains the "main" piece of code.</p>
<p>This dot-sourcing trick essentially tells the first script to go find the referenced file and paste that file's contents in at this dot-source location. When the script runs, that second file's contents replace this line, so it's just like all that second file's contents are in this file, but without the eyeball-clutter of having those contents actually in this file.</p>
<p>Now we can focus just on the mechanics of working with the tree. We'll look at that in <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-adding-treeview_28.html">"Notes About PowerShell: Adding a TreeView to the GUI - Part 2"</a>.</p>
</body>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-47700027585275427392022-10-27T08:55:00.047-05:002022-10-30T19:51:30.191-05:00Notes About PowerShell: A More Complex GUI
<head>
<style type="text/css">
p { line-height: 115%; margin-bottom: 0.1in; background: transparent }
p.coding-western { font-family: "Bitstream Vera Sans Mono", monospace; font-size: 8pt; line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-cjk { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-ctl { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
a:link { color: #000080; so-language: zxx; text-decoration: underline }
pre {
padding: 10px;
border: 1px solid black;
margin: 0;
background-color: Gainsboro;
font-size: smaller;
}
</style>
</head>
<body dir="ltr" lang="en-US" link="#000080" vlink="#800000"><p>Building
on the simplest GUI (see <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-simplest-gui.html">here</a>);
also for more info, see <a href="https://lazyadmin.nl/powershell/powershell-gui-howto-get-started/">The
Lazy Admin</a>), we can modify the parent window's size, title, and
background color, with the following script:</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form
$ParentWindow = New-Object System.Windows.Forms.Form
# Define the size, title, and background color
$ParentWindow.ClientSize = '500,300'
$ParentWindow.text = "My Parent Window"
$ParentWindow.BackColor = "#ffffff"
# Display the form
$ParentWindow.ShowDialog()
</pre>
<p>Now let's add an "OK" button:</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form
$ParentWindow = New-Object System.Windows.Forms.Form
# Define the size, title, and background color
$ParentWindow.ClientSize = '500,300'
$ParentWindow.text = "My Parent Window"
$ParentWindow.BackColor = "#ffffff"
# The OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button'
$ParentWindow.Controls.Add($buttonOK)
# Display the form
$ParentWindow.ShowDialog()
</pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUxV8ctefAZmMtOX_3VxMb-wMQ67OPGrcnaDKai4TkVFRCv65UkszAjavkd-iku8OJ4OKMVNKaLgcGxWz4gy8FqeF_kVEDyx994vFAy2fvdgPfaji5qpxspgg61whBfA7f5OukL3XTbfCLQnGyoMFwuNRmEgGJAk75STP-UzV1saPd0_r3/s1281/BlankOK.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="800" data-original-width="1281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUxV8ctefAZmMtOX_3VxMb-wMQ67OPGrcnaDKai4TkVFRCv65UkszAjavkd-iku8OJ4OKMVNKaLgcGxWz4gy8FqeF_kVEDyx994vFAy2fvdgPfaji5qpxspgg61whBfA7f5OukL3XTbfCLQnGyoMFwuNRmEgGJAk75STP-UzV1saPd0_r3/s320/BlankOK.png"/></a></div>
<p>When you run this script, you should see a rectangle inside the
main window. It doesn't have a label, is in the wrong spot, and
doesn't do anything. Let's make some changes. Also, "$ParentWindow"
is a lot of typing. We could shorten it by two letters to
"$MainWindow", but we don't have any daughter- or
subordinate windows, so we could just call it "$Window",
but let's save even more typing:</p>
<pre>
# Initialize the PowerShell GUI.
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form.
$Win = New-Object System.Windows.Forms.Form
# Define the size, title, and background color.
$Win.ClientSize = '500,300'
$Win.text = "My Parent Window"
$Win.BackColor = "#ffffff"
# The OK button.
$Okee-Dokee = New-Object 'System.Windows.Forms.Button' # Create the button.
$Okee-Dokee.Text = "Okely-dokely, good neighbor!" # Puts text on the button.
$Okee-Dokee.Location = '400, 240' # 100 pixels from the 500 edge.
$Win.Controls.Add($Okee-Dokee) # Add the button to the form.
# Display the form
$Win.ShowDialog()
</pre>
<p>Better, but still not quite there. Let's add a size spec (and rename the button control back to something more meaningful):</p>
<pre> ...
# The OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button' # Create the button.
$buttonOK.Text = "Okely-dokely, good neighbor!" # Puts text on the button.
$buttonOK.Location = '400, 240' # 100 pixels from the 500 edge.
$buttonOK.Size = '175, 23'
$Win.Controls.Add($buttonOK) # Add the button to the form.
...</pre>
<p>And now move the button:</p>
<pre> ...
# The OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button # Create the button.
$buttonOK.Text = "Okely-dokely, good neighbor!" # Puts text on the button.
$buttonOK.Location = '300, 240' # 200 pixels from the 500 edge.
$buttonOK.Size = '175, 23'
$Win.Controls.Add($buttonOK) # Add the button to the form.
...</pre>
<p>Note that it doesn't matter what order these elements are defined,
as long as they are defined before the "Add".</p>
<p>Now let's have the button do something. We'll have it close the
window, and print the message "OK" to the console.
(Allowable results are "None, OK, Cancel, Abort, Retry, Ignore,
Yes, No".) We'll also set the button to be the "I accept
the window as it is, so close it" button.</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form
$Win = New-Object System.Windows.Forms.Form
# Define the size, title and background color
$Win.ClientSize = '500,300'
$Win.text = "My Parent Window"
$Win.BackColor = "#ffffff"
# The OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button'
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.text = "Okeley-dokely, good neighbor!"
$buttonOK.Size = '175, 23'
$buttonOK.Location = '300, 240'
$buttonOK.DialogResult = "OK"
$Win.Controls.Add($buttonOK)
$Win.AcceptButton = $buttonOK
# Display the form
$Win.ShowDialog()
Write-Host("The button was pressed, returning the response: $($Win.DialogResult)")
</pre>
<p align="left" style="margin-bottom: 0in;">Now let's put a text box
on the form, pre-fill it with some text, pre-select that text, and
then return whatever text is in the text box when the OK button is
pressed. Let's also put a label atop the text. The changes to the
script are bolded below.</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form
$Win = New-Object System.Windows.Forms.Form
# Define the size, title and background color
$Win.ClientSize = '500,300'
$Win.text = "My Parent Window"
$Win.BackColor = "#ffffff"
# The OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button'
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.text = "Okeley-dokely, good neighbor!"
$buttonOK.Size = '175, 23'
$buttonOK.Location = '300, 240'
$buttonOK.DialogResult = "OK"
$Win.Controls.Add($buttonOK)
$Win.AcceptButton = $buttonOK
<strong># The Name textbox.
$NameBox = New-Object "System.Windows.Forms.Textbox"
$NameBox.Size = "175,23"
$NameBox.Location = "10,40"
$NameBox.Text = "Babushka"
$Win.Controls.Add($NameBox)
$Win.ActiveControl = $NameBox
# And its label.
$Label_NameBox = New-Object 'System.Windows.Forms.Label'
$Label_NameBox.Text = "Enter your name:"
$Label_NameBox.Size = "175,23"
$Label_NameBox.Location = '10,20'
$Win.Controls.Add($Label_NameBox)</strong>
# Display the form
$Win.ShowDialog()
Write-Host("The button was pressed, returning the word: $($Win.DialogResult)")
# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK")
{
Write-Host("The OK button was pressed. The name in the box is `"$($NameBox.Text)`".")
}
elseif ($Win.DialogResult -eq "Cancel")
{
Write-Host("The X was pressed. I`'m not going to tell you the name that is in the box.")
} # end of If</pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipDk3gQOs3lay6iAC1mzrqVbLESsb0Ui5uvpCnCjlad2fRS6nwDA7EYD0LDvx9DeFFUZ7h6Iel7153PDvH45Kj-ZeXORIOiSAY7tMEGP84vy4Jq0FHT86QOprVNanDoxdfyTg2hMGdA5jQg0IiYGyyT7YE8DGF5Npv0AbNR5erHb0O1DyX/s1281/ComplexGUI.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="800" data-original-width="1281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipDk3gQOs3lay6iAC1mzrqVbLESsb0Ui5uvpCnCjlad2fRS6nwDA7EYD0LDvx9DeFFUZ7h6Iel7153PDvH45Kj-ZeXORIOiSAY7tMEGP84vy4Jq0FHT86QOprVNanDoxdfyTg2hMGdA5jQg0IiYGyyT7YE8DGF5Npv0AbNR5erHb0O1DyX/s320/ComplexGUI.png"/></a></div>
<p align="left" style="margin-bottom: 0in;">If you wanted to
pre-select just a portion of the default text of the text box:</p>
<pre> ...
$NameBox.Text = "Babushka"
<strong>$NameBox.SelectionStart = 4
$NameBox.SelectionLength = 3</strong>
$Win.Controls.Add($NameBox)
...</pre>
</body>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-26024510764735353372022-10-27T08:45:00.007-05:002022-10-30T18:40:19.723-05:00Notes About PowerShell: The Simplest GUI<head>
<style type="text/css">
p { line-height: 115%; margin-bottom: 0.1in; background: transparent }
p.coding-western { font-family: "Bitstream Vera Sans Mono", monospace; font-size: 8pt; line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-cjk { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
p.coding-ctl { line-height: 100%; text-align: left; margin-bottom: 0.1in; border: 1px solid #000000; padding: 0.02in; background: #eeeeee; page-break-before: auto }
a:link { color: #000080; so-language: zxx; text-decoration: underline }
pre {
padding: 10px;
border: 1px solid black;
margin: 0;
background-color: Gainsboro;
font-size: smaller;
}
</style>
</head>
<body dir="ltr" lang="en-US" link="#000080" vlink="#800000">
<p>Create the following script (in PowerShell ISE), and then run it:</p>
<pre>
# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
# Create a new window form
$MasterFrame = New-Object System.Windows.Forms.Form
# Display the form
$MasterFrame.ShowDialog()
</pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3Col6nIlYHyjYBBG8r5hIdVz3_CqhwqUDiQ63AglyNlBA0Ah88NojvBf96b5INp3VQCN_IwIFShnsGlNxWzi3noLHPn9XjZqrWWgJ-xYNQV64FBzj_TE638flSRsqpgWaa8vEy17i1uc_I7JiscwVSTJc_6D-yNlF6CtkPvntSFtIvoQv/s1281/BlankWindowForm.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="800" data-original-width="1281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3Col6nIlYHyjYBBG8r5hIdVz3_CqhwqUDiQ63AglyNlBA0Ah88NojvBf96b5INp3VQCN_IwIFShnsGlNxWzi3noLHPn9XjZqrWWgJ-xYNQV64FBzj_TE638flSRsqpgWaa8vEy17i1uc_I7JiscwVSTJc_6D-yNlF6CtkPvntSFtIvoQv/s320/BlankWindowForm.png"/></a></div>
<p>That's all there is to creating a GUI window using PowerShell. Here's another rendition, just for comparison:</p>
<pre>
# Import the GUI pieces
Add-Type -AssemblyName System.Windows.Forms
# Create a new form<
$form_1 = New-Object System.Windows.Forms.Form
# Display the form<br />$form_1.ShowDialog()
</pre>
<p>For a more complex look, see <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-more-complex-gui.html">here.</a></p>
</body>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-68155584917382333562022-10-25T14:54:00.009-05:002022-10-25T14:54:55.153-05:00Notes About Powershell: Quickie About ADSI in Powershell on non-AD-bound Windows PC, to access AD<h2>Two Methods for Accessing AD Info from Powershell</h2>
<p>There are two basic methods for accessing Active Directory
information from within Powershell scripts: using Active Directory
Service Interfaces (<em>ADSI</em>), and the Powershell ActiveDirectory module.</p><p>This article is about ADSI.</p><p><em>ADSI</em> is the method I'm using below. There are two basic ADSI tools used with this method: <strong>[adsi]</strong> is an accelerator (or "alias") to <strong>System.DirectoryServices.DirectoryEntry</strong>, which points to actual objects within AD, and <strong>[adsisearcher]</strong> is an accelerator to <strong>System.DirectoryServices.DirectorySearcher</strong>, which is used for searching through AD. </p><h2>Using the ADSI Method for accessing information in Active Directory</h2><div style="text-align: left;">Suppose you want to search, from within Powershell, for data in Active Directory on a Windows PC that is not bound to a domain, but is on the same network as a domain server.</div><div style="text-align: left;"> </div><div style="text-align: left;">You'll need to tell the Searcher what domain credentials to use.</div><div style="text-align: left;"> </div><div style="text-align: left;">To do this:</div><div style="text-align: left;"> </div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"># Prompt user for creds; store them in $creds.UserName and $creds.Password<br /></span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$creds = Get-Credential</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"> </span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"># Specify the domain we want to search.</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$DomainName = "LDAP://mydomain.com/DC=mydomain,DC=com" </span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"><br /></span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"># Create a directory-entry object to that domain, with appropriate creds.<br /></span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$DirEntry = New-Object `</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"> -TypeName System.DirectoryServices.DirectoryEntry `</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"> ArgumentList $DomainName,</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"> $creds.UserName,</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"> $($creds.GetNetworkCredential().Password)</span></span></div><div style="text-align: left;"><br /></div><div style="text-align: left;">We now have the Directory Entry object that points to the root of the Active Directory tree, along with the credentials needed for accessing that root.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Now we're ready to build our Searcher, and then to run it.</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$Searcher = New-Object -type System.DirectoryServices.DirectorySearcher</span></span></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Notice the similarity in types between the Searcher object and the Directory Entry object. That's the difference between <i>[adsi]</i> and <i>[adsisearcher]</i> you might see elsewhere.</div><div style="text-align: left;"> </div><div style="text-align: left;">Now we'll plug our Directory Entry object into the Searcher object:</div><div style="text-align: left;"> </div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$Searcher.SearchRoot = $DirEntry </span></span></div><div style="text-align: left;"><br /></div><div style="text-align: left;">And now we'll do our search (not limiting it in any way; expect a deluge of info).</div><div style="text-align: left;"> <br /></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$Searcher.FindAll()</span></span></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Splooge!<br /></div><p></p>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-28242710248461547752022-10-23T18:55:00.002-05:002022-10-26T10:36:08.580-05:00Notes About Powershell: Getting Credentials<p style="text-align: left;">Sometimes when writing a Powershell script you need user credentials. Here are a few ways of doing that. We'll be using "johndoe" as the username, and "SuperSecret" as the password.<br /></p><h1 style="text-align: left;">The Simplest Way - Hardcode them in your command</h1><p style="text-align: left;">This is also the ugliest and least secure way. In this hypothetical case, the commands simply expect the username and password as arguments to the command:<br /></p><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">command_1 "johndoe" "SuperSecret"</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">command_2 "johndoe" "SuperSecret"</span></span></div><h1 style="text-align: left;">Use Variables</h1><p style="text-align: left;">Not quite as ugly, but still ugly.</p><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">$UserName = "johndoe"</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">$Password = "SuperSecret"<br /></span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">command_1 $UserName $Password</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">command_2 $UserName $Password</span></span></div><h1 style="text-align: left;">Prompt the User for a PSCredential Object</h1><p style="text-align: left;">Now we're getting to a more secure option.</p><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">$Creds = Get-Credential</span></span></div><p style="text-align: left;">This will pop up a window, prompting the user to enter his username and password. The script can then access the username with:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$Creds.UserName</span></span></div><p style="text-align: left;">and the password with:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$Creds.Password</span></span></div><p style="text-align: left;">Assuming our commands take credentials in the format of a PSCredential object, the commands might look like this:</p><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">command_1 $Creds</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-size: x-small;"><span style="font-family: courier;">command_2 $Creds</span></span></div><p style="text-align: left;">But if they require an actual username/password, you might think that this will work:</p><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-family: courier;"><span style="font-size: x-small;">command_1 $Creds.UserName $Creds.Password</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > </span></span><span style="font-family: courier;"><span style="font-size: x-small;">command_2 $Creds.UserName $Creds.Password</span></span></div><p style="text-align: left;">But it won't, because the "$Creds.Password" value is in a special format itself, called a "SecureString". You can see this by simply typing the name of the variable:</p><p style="text-align: left;"></p><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">PS > $Creds = Get-Credential</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">cmdlet Get-Credential at command pipeline position 1</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">Supply values for the following parameters:</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"><br />PS > $Creds</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"><br />UserName Password</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">-------- --------</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">johndoe System.Security.SecureString</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"><br /></span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"><br />PS > $Creds.UserName</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">johndoe</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;"><br />PS > $Creds.Password</span></span></div><div style="text-align: left;"><span style="font-size: x-small;"><span style="font-family: courier;">System.Security.SecureString</span></span></div><p style="text-align: left;">But it's easy to convert that SecureString back into plain text:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > $Creds.GetNetworkCredential().Password</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">SuperSecret</span></span></div><p style="text-align: left;">So now our commands would become:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > command_1 $Creds.UserName $Creds.GetNetworkCredential().Password</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > command_2 $Creds.UserName $Creds.GetNetworkCredential().Password</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;"> </span></span><br />Ideally our command would be a Powershell command that natively understands the PSCredential format:</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > PScommand_1 --Credential $Creds</span></span> <span style="font-family: courier;"><span style="font-size: x-small;"><br /></span></span></div><h1 style="text-align: left;">Load Up a PSCredential Object From Within the Script</h1><p style="text-align: left;">This is another insecure method, but should be known about.</p><p style="text-align: left;">Since, as mentioned before, the Credential object requires a SecureString for the password, we first need to create a SecureString password: <br /></p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$pwd = ConvertTo-SecureString "SuperSecret" -AsPlainText -Force</span></span><br /></div><p style="text-align: left;">Normally the "ConvertTo-SecureString" routine expects its input to be in the format of an encrypted string of text. It's "native languages" are SecureString and Encrypted String. You can see the Encrypted String format by converting the other way:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$pwd | ConvertFrom-SecureString</span></span></div><p style="text-align: left;">which will produce something that looks like this:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">01000000d08c9ddf0115d1118c7a00c04fc297eb01000000fde23916e5b4734ab18e12ca886af24900000000020000000000106600000001000020000000ff6caf244bddbe34bc6f7c8b9768317738d85d9008de0d3acaac4ee9133bc4870<br />00000000e80000000020000200000008ce0cd22aa3fcbb938f08f640201e1bb1c91500b679346a02e48815a385101a2200000006e2055f816b37e010e1938e59938d244c6170307ef651db8e95920f0c06b16d540000000f6b72c9148c628<br />c579209cd761fd0a2d6ebbdfbae888462733b06f08aec293e02f3a62b83ded1a10d24e370ab5b584bc31bf1816273e8761577b0a7455545881</span></span><br /></div><p style="text-align: left;">So to be clear, the "ConvertTo.../...From..." routines convert (natively) between SecureString and Encrypted Strings, not to/from plain text. To include the plain text in the process, we have to tell the "ConvertTo..." routine that its input is "AsPlainText", and to get the plain text back out, we have to use a completely different routine, as we did above, the "GetNetworkCredential()" routine. <br /></p><p style="text-align: left;">Now that we have the password as a SecureString, we can create the PSCredential object:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">$Creds = New-Object System.Management.Automation.PSCredential ("johndoe", $pwd)</span></span><br /></div><p></p><p>And now you can use the PSCredential object just as we did above:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > command_1 $Creds.UserName $Creds.GetNetworkCredential().Password</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PC > PScommand_1 --Credential $Creds <br /></span></span></div><p></p><h1 style="text-align: left;">Load Up a PSCredential Object From an External File</h1><p style="text-align: left;">This is kind of a compromise between the security of prompting the user for the credentials and having the credentials associated with the script itself. The password will be in an Encrypted format, and in a separate file, but it would still be pretty easy for a "hacker" to grab the file and read the password. So it's certainly not secure. You could put the external file on a secure server, but then you'd have to deal with the credentials for logging into the secure server, so all that does is kick the problem down the road a ways. Still, it might be the best we can do without going to extremes.</p><p style="text-align: left;">But the most onerous caveat about this method, at least for general purpose scripts, is that the password encryption can only be decrypted by the same user account on the same computer as was used to do the encryption. You can't write/develop the script that uses this method on one computer, and then run it on another computer, or as a different user.<br /></p><p style="text-align: left;">Some of the following is likely a repeat of what was said above; it's a copy-and-paste from another article I had started/<br /></p><h4>Reading the Creds From a File</h4>
<p>If you don't have a user sitting in front of the computer when the
script runs, you can store the username and an encrypted form of the
password in a file, beforehand, and then read it from that file when
it's needed. Be aware that this is still not <i>secure</i>, but it's more so.</p>
<h5>Saving the Credentials to an External File, Beforehand</h5>
<p>Not in your script, but just at a Powershell prompt, enter the following:</p><span style="font-family: courier;"><span style="font-size: x-small;">PS > Set-Content -Path ".\extras.zip" -Value "johndoe"<br /></span></span><p>This step creates a file named "extras.zip", over-writing any
existing files of that name, in the current directory. The file contains
the username. You can verify the contents of this newly-created file
with:</p><span style="font-family: courier;"><span style="font-size: x-small;">PS > type ".\extras.zip"<br /></span></span><p>As you can see, it's not really a .zip file; it's just a plain text
file. But naming it as a .zip is a minor mindgame, hoping to discourage
the casual "hackers" from bothering to try and open / look into the
file. It's a weak form of "security via obscurity"; probably completely
useless, but I know when I'm casually looking at files with which I'm
unfamiliar, I tend to look at .txt files and to leave .zip files along.
But you can name your file any way you want.</p><span style="font-family: courier;"><span style="font-size: x-small;">PS > Add-Content -Path ".\extras.zip" -Value $("SuperSecret") | ConvertFrom-SecureString -AsPlainText -Force)<br /></span></span><p>This step adds the username to the now-already-existing "extras.zip"
file. The value that gets added to the file is the password, which
before getting added to the file, is piped to a converter that takes the
plain text of "SuperSecret" and converts it to a <i>SecureString</i>.</p>
<p>You should now have a file named "extras.zip" with something like the following (which you can see with "<b>type .\extras.zip</b>":</p><span style="font-family: courier;"><span style="font-size: x-small;">johndoe
01000000d08c9ddf0115d1118c7a00c04fc297eb010000001150a84662a6cd45af512286977fabcc00000000020000000000106600000001000020000000943962d675717d4a12cfe44a8a22b6a5f0ca77762c7bb61e6929515af684db5d000000000e80000000020000200000006370779b6f82983d4f417eccad5be5a80b0ae8a71d2820e736862d2b25453ce220000000c96767ea12fd28ab840fcefb49b4f86d84e97f4676e806a8d7511a86532d3c3f400000008a26d44b1d1df760436be9be186cb87a6cca6220364752e435af61188330043ac201f634ff88fd751b17aff9394c00e6ada09a2f1dd93c27702f9802e813f90d<br /></span></span><p>Normally the <i>ConvertTo-SecureString</i> expects its input to be a standard <i>encrypted</i>
string of plain text, like the line with numbers above. But we're
starting with the plain text of "SuperSecret". If you feed it this plain
unencrypted text, it complains. You can see this for yourself with this
command:</p><span style="font-family: courier;"><span style="font-size: x-small;">PS > "SuperSecret" | ConvertTo-SecureString<br /></span></span><p>That's why we use the "-AsPlainText" argument, to override that behavior. You can see this for yourself like so:</p><span style="font-family: courier;"><span style="font-size: x-small;">PS > 'SuperSecret" | ConvertTo-SecureString -AsPlainText<br /></span></span><p>But then you get another complaint. For security reasons, the command
doesn't like to be given secret info out in the open, but you can force
it to accept the input anyway, with:</p><span style="font-family: courier;"><span style="font-size: x-small;">PS > 'SuperSecret" | ConvertTo-SecureString -AsPlainText -Force<br /></span></span><p>and now you see that the result is a SecureString.</p>
<p>However, we can not write this SecureString out to a file, and then
recover it later. But what we can do is reverse the process, part-way.
We won't reverse it back to a plain-text "SuperSecret", but rather to an
encrypted-text form of "SuperSecret", by piping the above command to
the counterpart:</p><p>
</p><pre><span style="font-family: courier;"><span style="font-size: x-small;">PS > 'SuperSecret" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString</span></span></pre>
<p>The "native language" of these two tools is SecureStrings on one side
and encrypted text on the other, so we don't have to specify anything
special; it just takes the SecureString in the previous command and
converts from that into encrypted text.</p>
<p>Now that we have the credentials stored in an external file, we have to tell our script to read them.</p>
<h5>Reading the Credentials from an External File</h5><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > $contents = Get-Content -Path ".\extras.zip"</span></span></div><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > $Creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $contents[0], $($contents[1] | ConvertTo-SecureString)</span></span></div><div><span style="font-family: courier;"></span></div><p></p><h5 style="text-align: left;">Use the PSCredential Object<br /></h5><p style="text-align: left;">And now you can use the PSCredential object just as we did above:</p><div style="text-align: left;"><span style="font-family: courier;"><span style="font-size: x-small;">PS > command_1 $Creds.UserName $Creds.GetNetworkCredential().Password</span></span></div><span style="font-family: courier;"><span style="font-size: x-small;">PC > PScommand_1 --Credential $Creds </span></span><p style="text-align: left;"> </p><p></p><p></p><p style="text-align: left;"></p><p style="text-align: left;"></p><p style="text-align: left;"></p> Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-75691544849844714742022-10-18T13:39:00.009-05:002022-10-27T08:46:37.535-05:00Set Up Bridged Networking on Debian for QEMU/KVM (Virtual Machine Manager)<h2>Step One: Create a New Bridged Network Interface</h2>
<p>Without the VM running, create a virtual bridged interface. Create a file named "br0" in <i>/etc/networking/interfaces.d</i>, with the following contents:</p>
<pre><small>auto br0
iface br0 inet dhcp
pre-up ip tuntap add dev tap0 mode tap user <username who will run virtual machine>
pre-up ip link set tap0 up
bridge_ports all tap0
bridge_stp off
bridge_maxwait 0
bridge_fd 0
post-down ip link set tap0 down
post-down ip tuntap del dev tap0 mode tap</small></pre>
<h2>Step Two: Restart networking.</h2>
<p>Before you restart networking, and then again after you restart networking, you can run:
</p><pre><small>ip link show type bridge</small></pre>
<p>and</p>
<pre><small>ip link show master br0</small></pre>
<p>to see some before-and-after stats on what you're accomplishing.</p>
<pre><small>sudo systemctrl restart networking</small></pre>
<p>or</p>
<pre><small>sudo /etc/init.d/networking restart</small></pre>
<h2>Start Virtual Machine Manager and configure the VM to use the new bridged interface.</h2>
<p>Once the Virtual Machine Manager is running, <i>Open</i> the desired VM, and then in the <i>View</i> menu, select <i>Details</i>. Select the <i>NIC...</i> item in the left-hand pane, and then in the right-hand pane, change the <i>Network Source</i> from "Virtual network 'default' : NAT" to "Bridged device...", and then in the <i>Device name:</i> field, enter "br0" to match the name of the interface defined in Step One above.</p>
<p>Now power-on your VM, and it should connect via bridged networking instead of being NAT-ted.</p><p><i>CAVEAT:</i> As I discovered later, this does not work with a wi-fi host; the host needs to be wired to Ethernet. I think it can be made to work (at least in some cases), but it looks to be complicated.<br /></p>
<p></p>Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-80575876892319513762022-10-17T15:09:00.008-05:002022-10-17T15:32:02.592-05:00Notes About Powershell: Very Basic Powershell ISE Usage<p>Start by opening the Powershell ISE. It should be available in the "Type here to search" window of Windows 10, or similar. Note that the ISE (Integrated Scripting Environment) is essentially a simple Integrated Development Environment (IDE). Initially, it'll likely open only to a blue screen where you are in the Powershell "shell", where you can type commands, etc. For example, type:</p>
<pre><small>dir</small></pre>
<p>to get a listing of the current <em>dir</em>ectory. This is a backwards-compatible (to the days of MS-DOS, even) command. The Powershell equivalent of doing the same thing is:</p>
<pre><small>Get-Childitem</small></pre>
<p>Yes, that does seem like over-kill, but Microsoft is going for consistency within the Powershell environment, with every command having an approved verb, <strong>Get</strong> in this case, followed by the "gist" of what the command is about, with the <strong>Childitem</strong> referring to the directory items.<p>
<p>You can put these Powershell-type commands in a script, to run like a program. To do this, go to <em>File/New</em> to open a new script window.
<p>In here, you can put your <strong>dir</strong> command. Or better, use the Powershell equivalent: <strong>Get-Childitem</strong>. You might see the ISE trying to help you find the correct command/item with it's type-ahead prompts. (And you might not; it seems to be pretty hit-or-miss.)</p>
<p>Now, run your script by clicking on the green arrow in the menubar area. The results should show in the blue shell area below the text editing area.</p>
<p>Now if you save the script to a file, it'll automatically save with a ".ps1" extension, and you can open it later for editing. But once you save it, Powershell considers it a "real" script, and will refuse to run it because running scripts is against the default Powershell policy. Give it a try to see what I mean (assuming it hasn't already been fixed on your machine).</p>
<p>To fix that, fire up a Powershell session as Administrator, and run:</p>
<pre><small>PS > Set-ExecutionPolicy</small></pre>
<p>and select the "All" option. You can then exit out of the Admin session of Powershell, and restart your normal-user session of the Powershell ISE, and you should now be able to run your script.</p>
Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-6231214553293960742022-10-17T14:57:00.003-05:002022-10-17T14:58:30.693-05:00Notes About Powershell: Accelerators<h2>Accelerators</h2>
<p>It is my understanding that many items in square brackets are "accelerators", which are what I would know as "aliases". You can get a list of Powershell objects that are accelerators like this:</p>
<pre><small>PS > [System.Management.Automation.PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::get
Key Value
--- -----
Alias System.Management.Automation.AliasAttribute
AllowEmptyCollection System.Management.Automation.AllowEmptyCollectionAttribute
AllowEmptyString System.Management.Automation.AllowEmptyStringAttribute
AllowNull System.Management.Automation.AllowNullAttribute
ArgumentCompleter System.Management.Automation.ArgumentCompleterAttribute
array System.Array
bool System.Boolean
...
pslistmodifier System.Management.Automation.PSListModifier
psobject System.Management.Automation.PSObject
pscustomobject System.Management.Automation.PSObject
psprimitivedictionary System.Management.Automation.PSPrimitiveDictionary
...
CimSession Microsoft.Management.Infrastructure.CimSession
adsi System.DirectoryServices.DirectoryEntry
adsisearcher System.DirectoryServices.DirectorySearcher
wmiclass System.Management.ManagementClass
wmi System.Management.ManagementObject
wmisearcher System.Management.ManagementObjectSearcher
mailaddress System.Net.Mail.MailAddress
scriptblock System.Management.Automation.ScriptBlock
psvariable System.Management.Automation.PSVariable
...</small></pre>
<p>
Notice the <strong>psobject</strong> accelerator. It's an accelerator ("alias") to <strong>System.Management.Automation.PSObject</strong>. This means that the above command can be shortened to:
</p>
<pre><small>PS > [psobject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::get</small></pre>
<p>Read more about accelerators <a href="https://devblogs.microsoft.com/scripting/use-powershell-to-find-powershell-type-accelerators/">here</a>.</p>
Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0tag:blogger.com,1999:blog-14696592.post-29095961349959380772022-10-15T10:08:00.075-05:002022-10-26T10:36:57.714-05:00Notes About Powershell, adsi, Active Directory Module, alternative authentication, Basics of Accessing Active Directory<h2>Two Methods for Accessing AD Info from Powershell</h2>
<p>There are two basic methods for accessing Active Directory information from within Powershell scripts: using Active Directory Service Interfaces (<em>ADSI</em>), and the Powershell ActiveDirectory module.</p>
<p>The second method, using the ActiveDirectory module, is native to Powershell, but is not "built-in" to most Powershell installations. Therefore, if you're planning for your Powershell to run on multiple computers, you have to take actions to make sure tht module is installed. This adds complexity to your script, and possible time to the run-time of the script.<p>
<p>The first method is "built in" to Powershell (sort of), but not native to Powershell. It is basically an import from the .NET system that is already installed on most Windows systems. It is this immediate and reliable availability that makes me prefer the ADSI method over the ActiveDirectory module method. Otherwise I'd stick with the pure Powershell method.</p>
<p><em>ADSI</em> is the method I'm using below. There are two basic ADSI tools used with this method: <strong>[adsi]</strong> is an accelerator (or "alias") to <strong>System.DirectoryServices.DirectoryEntry</strong>, which points to actual objects within AD, and <strong>[adsisearcher]</strong> is an accelerator to <strong>System.DirectoryServices.DirectorySearcher</strong>, which is used for searching through AD.
<h2>Using the ADSI Method for accessing information in Active Directory</h2>
<h4>On a Machine Already Bound to an AD Domain</h4>
<p>To access Active Directory (AD) data, we must <em>bind</em> to the directory. If your computer is attached to a network on which resides an AD controller, and your computer is already bound to that controller's domain, and you're logged into that domain, you can simply run:
<pre><small>[adsi]''</small></pre>
<p>at a Powershell prompt, or from a Powershell script. This command will return the <em>distinguishedName</em> of the domain to which the computer is bound. Your results should be something like this:</p>
<pre><small>distinguishedName : {DC=acu,DC=local}
Path : </small></pre>
<p><strong>adsi</strong> is an <em>accelerator</em> ("alias" - see <a href="https://kentwest.blogspot.com/2022/10/notes-about-powershell-accelerators.html">here</a> for more info) for <strong>System.DirectoryServices.DirectoryEntry</strong>. The equivalent command is:</p>
<pre><small>[System.DirectoryServices.DirectoryEntry]''</small></pre>
<p>I mention using <strong>adsi</strong> because you might see it elsewhere. But for clarity, I'll use the more verbose verbiage, <strong>System.DirectoryServices.DirectoryEntry</strong>.</p>
<h4>On a Machine Not Bound to an AD Domain</h4>
<p>If your computer is not bound to an AD controller, you'll get results like this:</p>
<pre><small>PS C:\Users\westk> [adsi]""
format-default : The following exception occurred while retrieving member "distinguishedName": "The specified domain either does not exist or could not be
contacted.
"
+ CategoryInfo : NotSpecified: (:) [format-default], ExtendedTypeSystemException
+ FullyQualifiedErrorId : CatchFromBaseGetMember,Microsoft.PowerShell.Commands.FormatDefaultCommand</small></pre>
<p>(Notice the quotes can be single or double; sometimes one will work better than the other depending on the situation.)</p>
<p>The computer I'm working with is not currently bound to the domain, but it is on the same network as the domain. In order to get AD info, I have to bind the computer in some way to the domain. This will require credentials for logging into the domain. If the computer were already on the domain, and I logged in as a domain user, this method would use my Windows login credentials by default. But we can specify different credentials, or just provide credentials if the computer is not already on the domain.</p>
<p>The <strong>System.DirectoryServices.DirectoryEntry</strong> method is picky about how credentials are presented. You have to give it the type of connection being made ("LDAP", as opposed to "WinNT", or one other method which I can't recall at the moment), and a username and a password that has domain permissions to read from the domain. For reading from this part of the domain, almost any domain user will suffice.</p>
<p>When giving this data to <strong>System.DirectoryServices.DirectoryEntry</strong>, the object it returns is "not compatible" with just spitting out the results to the console like it is when not giving this information; you'll need to declare the results as a new object. We'll call the new object "$root", since we're starting at the root of the Active Directory domain tree. If we wanted, we could call it "domain", or "DomainDN", or "bub". Suppose the username you're using to bind with is "johndoe", and his password is "SuperSecret". The command could thus look like this:</p>
<pre><small>$root = New-Object System.DirectoryServices.DirectoryEntry("LDAP://acu.local/DC=acu,DC=local","acu.local\johndoe","SuperSecret")</small></pre>
<p>Notice that the username field includes the domain name component.</p>
<p>When we look at the results:</p>
<pre><small>PS > $root</small></pre>
<p>this should produce output like this:</p>
<pre><small>distinguishedName : {DC=acu,DC=local}
Path : LDAP://acu.local/DC=acu,DC=local</small></pre>
<p>We could also write the one-liner variable-assignment as several lines (ending some lines with a back-tic to signify that the line is continued), perhaps making the declaration more readable:</p>
<pre><small>$root = New-Object `
-TypeName System.DirectoryServices.DirectoryEntry `
-ArgumentList "LDAP://acu.local/DC=acu,DC=local",
"acu.local\johndoe",
"SuperSecret"</small></pre>
<h3>Being a Little More Secure With the Password</h3>
<p>Although you can feed the credentials directly to this command as strings, that's not a very secure way to do it in a script. So let's use variables instead, including a variable for the path within the AD tree:</p>
<pre><small>$UserName = "johndoe"
$Password = "SuperSecret"
$AD_Path = "LDAP://acu.local/DC=acu,DC=local"
$root = New-Object `
-TypeName System.DirectoryServices.DirectoryEntry `
-ArgumentList $AD_Path,
$UserName,
$Password</small></pre>
<p>Now we'd need to do something about those credential assignments being out in the open like that.</p>
<h4>Secure-Prompting the User</h4>
<p>If your script is going to be interactively run by a user sitting in front of the computer, you can (securely) prompt the user for the creds, like so:</p>
<pre><small>$creds = Get-Credential
$root = New-Object `
-TypeName System.DirectoryServices.DirectoryEntry `
-ArgumentList $AD_Path,
$($creds.UserName),
$($creds.GetNetworkCredential().Password)</small></pre>
<p>(You can also prompt with a pre-loaded username field with <strong>$creds = Get-Credential -Credential "acu.local\johndoe"</strong> . Note also that this process doesn't <em>do</em> anything; it just puts these two values in the $creds variable, making the previous variables, <em>$UserName</em> and <em>$Passord</em>, superflous; get rid of them from your script.)</p>
<p>Note that the $creds.Password must be manipulated a bit to get it in an acceptable form to be used by this command. Looking at the values of the various objects...</p>
<pre><small>PS C:\Users\westk> $creds
UserName Password
-------- --------
acu.local\johndoe System.Security.SecureString
PS C:\Users\westk> $creds.UserName
acu.local\johndoe
PS C:\Users\westk> $creds.Password
System.Security.SecureString
PS C:\Users\westk> $creds.GetNetworkCredential().Password
SuperSecret</small></pre>
<p>you can start to understand the manipulations involved.</p>
<h4>Reading the Creds From a File</h4>
<p>If you don't have a user sitting in front of the computer when the script runs, you can store the username and an encrypted form of the password in a file, beforehand, and then read it from that file when it's needed. Be aware that this is still not <em>secure</em>, but it's more so.</p>
<p>Also, the most onerous caveat about this method, at least for general purpose scripts, is that the password encryption can only be decrypted by the same user account on the same computer as was used to do the encryption. You can't write/develop the script that uses this method on one computer, and then run it on another computer, or as a different user.</p>
<h5>Saving the Credentials to an External File, Beforehand</h5>
<p>Not in your script, but just at a Powershell prompt, enter the following:</p>
<pre><small>Set-Content -Path ".\extras.zip" -Value "acu.local\johndoe"</small></pre>
<p>This step creates a file named "extras.zip", over-writing any existing files of that name, in the current directory. The file contains the username. You can verify the contents of this newly-created file with:</p>
<pre><small>type ".\extras.zip"</small></pre>
<p>As you can see, it's not really a .zip file; it's just a plain text file. But naming it as a .zip is a minor mindgame, hoping to discourage the casual "hackers" from bothering to try and open / look into the file. It's a weak form of "security via obscurity"; probably completely useless, but I know when I'm casually looking at files with which I'm unfamiliar, I tend to look at .txt files and to leave .zip files along. But you can name your file any way you want.</p>
<pre><small>Add-Content -Path ".\extras.zip" -Value $("SuperSecret") | ConvertFrom-SecureString -AsPlainText -Force)</small></pre>
<p>This step adds the username to the now-already-existing "extras.zip" file. The value that gets added to the file is the password, which before getting added to the file, is piped to a converter that takes the plain text of "SuperSecret" and converts it to a <em>SecureString</em>.</p>
<p>You should now have a file named "extras.zip" with something like the following (which you can see with "<strong>type .\extras.zip</strong>":</p>
<pre><small>acu.local\johndoe
01000000d08c9ddf0115d1118c7a00c04fc297eb010000001150a84662a6cd45af512286977fabcc00000000020000000000106600000001000020000000943962d675717d4a12cfe44a8a22b6a5f0ca77762c7bb61e6929515af684db5d000000000e80000000020000200000006370779b6f82983d4f417eccad5be5a80b0ae8a71d2820e736862d2b25453ce220000000c96767ea12fd28ab840fcefb49b4f86d84e97f4676e806a8d7511a86532d3c3f400000008a26d44b1d1df760436be9be186cb87a6cca6220364752e435af61188330043ac201f634ff88fd751b17aff9394c00e6ada09a2f1dd93c27702f9802e813f90d</small></pre>
<p>Normally the <em>ConvertTo-SecureString</em> expects its input to be a standard <em>encrypted</em> string of plain text, like the line with numbers above. But we're starting with the plain text of "SuperSecret". If you feed it this plain unencrypted text, it complains. You can see this for yourself with this command:</p>
<pre><small>"SuperSecret" | ConvertTo-SecureString</small></pre>
<p>That's why we use the "-AsPlainText" argument, to override that behavior. You can see this for yourself like so:</p>
<pre><small>'SuperSecret" | ConvertTo-SecureString -AsPlainText</small></pre>
<p>But then you get another complaint. For security reasons, the command doesn't like to be given secret info out in the open, but you can force it to accept the input anyway, with:</p>
<pre><small>'SuperSecret" | ConvertTo-SecureString -AsPlainText -Force</small></pre>
<p>and now you see that the result is a SecureString.</p>
<p>However, we can not write this SecureString out to a file, and then recover it later. But what we can do is reverse the process, part-way. We won't reverse it back to a plain-text "SuperSecret", but rather to an encrypted-text form of "SuperSecret", by piping the above command to the counterpart:<p>
<pre><small>'SuperSecret" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString</small></pre>
<p>The "native language" of these two tools is SecureStrings on one side and encrypted text on the other, so we don't have to specify anything special; it just takes the SecureString in the previous command and converts from that into encrypted text.</p>
<p>Now that we have the credentials stored in an external file, we have to tell our script to read them.</p>
<h5>Reading the Credentials from an External File</h5>
<p>In your script, replace the "$creds = Get-Credential" line like so:</p>
<pre><small># $creds = Get-Credential
$contents = Get-Content -Path ".\extras.zip" # Read the file's contents.
# Reconvert the encrypted password back to a SecureString, and put both username and password into a new PSCredential object named $creds.
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $contents[0], $($contents[1] | ConvertTo-SecureString)</small></pre>
<h4>A Whole Script</h4>
<p>Here's a whole script, so you can see the big picture.</p>
<pre><small><# Powershell Script To Access An Active Directory Tree
Written By: [your name]
Date: [today's date]
[any ther comments you want to add]
#>
$creds = Get-Credential
$AD_Path = "LDAP://acu.local/DC=acu,DC=local"
$root = New-Object `
-TypeName System.DirectoryServices.DirectoryEntry `
-ArgumentList $AD_Path,
$($creds.UserName),
$($creds.GetNetworkCredential().Password)
Write-Output("The Distinguished (`"unique`") Name for the AD domain is: $($root.distinguishedName)")</small></pre>
<p>Save that as a Powershell script file, and run it, and you should see output similar to:<p>
<pre><small>The Distinguished ("unique") Name for the AD domain is: DC=acu,DC=local</small></pre>
<h4>A Second Whole Script, Slightly Different, for Perspective</h4>
<pre><small># Powershell Script To Access An Active Directory Tree
$Username_And_Password = [PSCustomObject]@{
UserName = "maryjane"
Password = "SuperTramp"
}
$TreeRoot = "LDAP://acu.local/DC=acu,DC=local"
$DirEntry = New-Object `
-TypeName System.DirectoryServices.DirectoryEntry `
-ArgumentList $($TreeRoot),
$($Username_And_Password.UserName),
$($Username_And_Password.Password)
Write-Output("The root is: $($TreeRoot).")
Write-Output("The password for $($Username_And_Password.UserName) is $($Username_And_Password.Password).")</small></pre>
<h4>One More Example</h4>
<pre><small>$AD_Node = adsi New-Object ("LDAP://DC=my_company,DC=com","my_company\accountant","time=$")
Write-Output("The domain info = $($AD_Node | Get-Member).")</small></pre>
<p>which will produce output like this:</p>
<pre><small>The domain info = static string ConvertDNWithBinaryToString(psobject deInstance, psobject dnWithBinaryInstance) static
long ConvertLargeIntegerToInt64(psobject deInstance, psobject largeIntegerInstance) System.DirectoryServices.Property
ValueCollection auditingPolicy {get;set;} System.DirectoryServices.PropertyValueCollection creationTime {get;set;} Sys
tem.DirectoryServices.PropertyValueCollection dc {get;set;} System.DirectoryServices.PropertyValueCollection distingui
shedName {get;set;} System.DirectoryServices.PropertyValueCollection dSASignature {get;set;} System.DirectoryServices.
PropertyValueCollection dSCorePropagationData {get;set;} System.DirectoryServices.PropertyValueCollection forceLogoff
{get;set;} System.DirectoryServices.PropertyValueCollection fSMORoleOwner {get;set;} System.DirectoryServices.Property
ValueCollection gPLink {get;set;} System.DirectoryServices.PropertyValueCollection instanceType {get;set;} System.Dire
ctoryServices.PropertyValueCollection isCriticalSystemObject {get;set;} System.DirectoryServices.PropertyValueCollecti
on lockoutDuration {get;set;} System.DirectoryServices.PropertyValueCollection lockOutObservationWindow {get;set;} Sys
tem.DirectoryServices.PropertyValueCollection lockoutThreshold {get;set;} System.DirectoryServices.PropertyValueCollec
tion masteredBy {get;set;} System.DirectoryServices.PropertyValueCollection maxPwdAge {get;set;} System.DirectoryServi
ces.PropertyValueCollection minPwdAge {get;set;} System.DirectoryServices.PropertyValueCollection minPwdLength {get;se
t;} System.DirectoryServices.PropertyValueCollection modifiedCount {get;set;} System.DirectoryServices.PropertyValueCo
llection modifiedCountAtLastProm {get;set;} System.DirectoryServices.PropertyValueCollection ms-DS-MachineAccountQuota
{get;set;} System.DirectoryServices.PropertyValueCollection msDS-AllUsersTrustQuota {get;set;} System.DirectoryServic
es.PropertyValueCollection msDS-Behavior-Version {get;set;} System.DirectoryServices.PropertyValueCollection msDS-Expi
rePasswordsOnSmartCardOnlyAccounts {get;set;} System.DirectoryServices.PropertyValueCollection msDS-IsDomainFor {get;s
et;} System.DirectoryServices.PropertyValueCollection msDs-masteredBy {get;set;} System.DirectoryServices.PropertyValu
eCollection msDS-NcType {get;set;} System.DirectoryServices.PropertyValueCollection msDS-PerUserTrustQuota {get;set;}
System.DirectoryServices.PropertyValueCollection msDS-PerUserTrustTombstonesQuota {get;set;} System.DirectoryServices.
PropertyValueCollection name {get;set;} System.DirectoryServices.PropertyValueCollection nextRid {get;set;} System.Dir
ectoryServices.PropertyValueCollection nTMixedDomain {get;set;} System.DirectoryServices.PropertyValueCollection nTSec
urityDescriptor {get;set;} System.DirectoryServices.PropertyValueCollection objectCategory {get;set;} System.Directory
Services.PropertyValueCollection objectClass {get;set;} System.DirectoryServices.PropertyValueCollection objectGUID {g
et;set;} System.DirectoryServices.PropertyValueCollection objectSid {get;set;} System.DirectoryServices.PropertyValueC
ollection otherWellKnownObjects {get;set;} System.DirectoryServices.PropertyValueCollection pwdHistoryLength {get;set;
} System.DirectoryServices.PropertyValueCollection pwdProperties {get;set;} System.DirectoryServices.PropertyValueColl
ection replUpToDateVector {get;set;} System.DirectoryServices.PropertyValueCollection repsFrom {get;set;} System.Direc
toryServices.PropertyValueCollection repsTo {get;set;} System.DirectoryServices.PropertyValueCollection rIDManagerRefe
rence {get;set;} System.DirectoryServices.PropertyValueCollection serverState {get;set;} System.DirectoryServices.Prop
ertyValueCollection subRefs {get;set;} System.DirectoryServices.PropertyValueCollection systemFlags {get;set;} System.
DirectoryServices.PropertyValueCollection uASCompat {get;set;} System.DirectoryServices.PropertyValueCollection uSNCha
nged {get;set;} System.DirectoryServices.PropertyValueCollection uSNCreated {get;set;} System.DirectoryServices.Proper
tyValueCollection wellKnownObjects {get;set;} System.DirectoryServices.PropertyValueCollection whenChanged {get;set;}
System.DirectoryServices.PropertyValueCollection whenCreated {get;set;}.</small></pre>
<h3>Get The Next Level of Objects in the Domain Tree</h3>
<p>After the last example, we now have the top lovel of our domain ("acu" in my case) in the variable <strong>$TreeRoot</strong> (or "$AD_Path", or "$root", or "AD_Node", or whatever variable name you chose to use; I'll use $Monkey in the following text, because we'll be watching where the monkey has climbed to in the tree).</p>
<p>Also, I have replaced the multi-line definition of the $Monkey object with the one-liner version.</p>
<p>Here's the script I currently have:</p>
<pre><small><# Scan_AD_Tree
.DESCRIPTION
This script accesses an Active Directory Tree
.AUTHOR
Kent West
October 2022
#>
# These creds of a domain user will be used to bind to the domain.
# We can either prompt the user for those credentials...
# $creds = Get-Credential
# ... or we can get the creds from an external text file, assuming the file has been previously created, with:
# Set-Content -Path ".\extras.zip" -Value "acu.local\johndoe"
# Add-Content -Path ".\extras.zip" -Value $("SuperSecret") | ConvertFrom-SecureString -AsPlainText -Force)
$contents = Get-Content -Path ".\extras.zip" # This reads the file, auto-making the "$contents" an array variable.
# Reconvert the encrypted-text password back to a SecureString, and put both username and password into a new PSCredential object named $creds.
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $contents[0], $($contents[1] | ConvertTo-SecureString)
# This is the LDAP "path" to the root of our AD domain "tree" named "acu.local".
$Tree_Path = "LDAP://acu.local/DC=acu,DC=local"
# We'll bind our tree-climber ("$Monkey") to the tree's path, currently at the root, using the domain user's creds.
$Monkey = New-Object System.DirectoryServices.DirectoryEntry($Tree_Path,$creds.UserName,$creds.GetNetworkCredential().Password)
Write-Output("The Common Name (CN) of the domain is $($Monkey.Name)."
<h3>Explanations/Comments in the Above May Not Be Entirely Accurate</h3>
<p>This has been a learning process for me; I'm not confident I've explained things above entirely correctly, and I'm tired of chasing this rabbit, so I'm going to stop here, with a link that I started to include, along with a piece of code that references itself for no reason I can think of. In the hidden comments of the source to this page is a lot more information that doesn't really belong here, but is related.</p>
<a href="https://lazywinadmin.com/2013/10/powershell-using-adsi-with-alternate.html">Source Info</a>
<pre><small>$DomainDN = New-Object -Type System.DirectoryServices.DirectorySearcher
$DomainDN.SearchRoot.Path</small></pre>
<!--
<h3>Under Construction - Here's most of the whole script for reference</h3>
<pre><small>
<#
AcquireHostnameAndOU-2023.ps1
.NOTES
--------------------------------------------------------------------------------
Organization: Abilene Christian University
Created by: Kent West, 10 Oct 2022, based partially on the work of others
--------------------------------------------------------------------------------
.DESCRIPTION
This script allows this PC's hostname and desired Active Directory Organizational Unit
to be set from the "MAC-to-NAME & Imaging Instructions" Google Doc spreadsheet stored
in ACU's Technology Support shared Google Drive, based on this PC's MAC-Layer address.
If the MAC is not found in that document, a window will open allowing the user to set
those things. The result will be a file that looks something like this:
Hostname:EDB-301-03-2232
OU Node:Business Intelligence Service Accounts
OU Path:OU=Business Intelligence Service Accounts,OU=Special Users,DC=acu,DC=local
#>
#----------------------------------------------
# GLOBAL CONSTANTS
#----------------------------------------------
$default_Selected_OU = "Digital Signs"
$default_Selected_OU_Path = "OU=Digital Signs,DC=acu,DC=local"
$default_Hostname = "EDB-123-01-2433"
$username = "[target domain]\[domain user with perms to read AD]"
$Credential = Get-Credential -Credential $username
# You could do the next two steps, instead of the one above, but it's insecure, leaving the password in open-text in a script
#$password = "[user's password]" | ConvertTo-SecureString -AsPlainText -Force
#$Credential = New-Object System.Management.Automation.PSCredential($username,$password)
#----------------------------------------------
# WHERE TO STORE RESULTS
#----------------------------------------------
$CONFIG_DIR = "C:\AnswerFile\"
$CONFIG_FILE = "config.txt"
$LOGFILE = "$($CONFIG_DIR)\CompNameAndOU.log.txt"
function Create-ConfigDirFile {
# Create the config directory (ignoring error if it already exists)
New-Item -ItemType Directory -Force -Path $CONFIG_DIR | Out-Null
log("================= $(Get-Date) ======================")
log("`"$($CONFIG_DIR)`" directory created.")
# If the config file already exists, delete it as it's presumably obsolete.
If (Test-Path $CONFIG_DIR$CONFIG_FILE){
log("Deleting existing `"$($CONFIG_DIR)$($CONFIG_FILE)`".")
Remove-Item $CONFIG_DIR$CONFIG_FILE
}
} # end of the Create-ConfigDirFile function
function log($string) {
Add-Content $LOGFILE "$(Get-Date) - $($string)"
} # end of the log() function
function Get-Data_From_File {
# .DESCRIPTION
# The OU and Hostname of this PC can be pre-configured in the Google Doc "MAC-to-NAME & Imaging Instructions"
# spreadsheet ("Computers" tab) in the shared Tech Support Team Drive. The following file is auto-generated
# whenever changes are made to that document.
$SOURCE_CSV = "https://docs.google.com/spreadsheets/d/e/2PACX-1vR8wwEVLwvMkcWC6AT5I3FX_ZoyeX31O7pEJ9AwWeEPeMlSjP1qswV02R0hnoRCz1DounQIXqG9tZt2/pub?gid=0&single=true&output=csv"
$Google_Doc_Data_Filename = "MAC-to-NAME & Imaging Instructions"
# Define the object in which to return results
$Results = [pscustomobject]@{
Hostname = ""
OU_Node = ""
OU_Path = ""
}
# Search the .CSV file and find a match, or prompt for that info
# Get this computer's MAC address
# $MY_MAC = Get-NetAdapter | foreach { $_.MacAddress } REM This command not available in KBE
# at the time this script was written; bummer. It's easier than the following stuff.
log("Getting MAC address.")
$MY_MAC = Get-WmiObject Win32_NetworkAdapterConfiguration | select -expand macaddress
# $MY_MAC = $MY_MAC.Replace(':','-')
log("This computer's MAC address is $MY_MAC.")
# Copy the web-published form to a local variable
log("Downloading `"$($Google_Doc_Data_Filename)`".csv file.")
$CSV = Convertfrom-csv (Invoke-WebRequest -UseBasicParsing -Uri $SOURCE_CSV).Content
# Uncomment to print it to logfile for debugging, if needed.
# log("The contents of the `"$($Google_Doc_Data_Filename)`".csv file are:")
# Add-Content $LOGFILE $CSV
# Search that file for this computer's MAC address
log("Searching `"$($Google_Doc_Data_Filename)`".csv for $MY_MAC.")
# Parse the three bits of info from the variable holding the file's contents
$Results.Hostname = $CSV | where {$_."MAC ADDRESS" -eq $MY_MAC} | % "Hostname"
$Results.OU_Node = $CSV | where {$_."MAC ADDRESS" -eq $MY_MAC} | % "Org Unit"
$Results.OU_Path = $CSV | where {$_."MAC ADDRESS" -eq $MY_MAC} | % "Full Org Unit"
# And log those results
log("Results of file-search:`n`tHostname:$($Results.Hostname)`n`tOU Node:$($Results.OU_Node)`n`tOU Path:$($Results.OU_Path)")
return $Results
} # end of the Get-Data_From_File function
function Get-Data_From_Prompt {
# .DESCRIPTION
# Opens a window for the user to select the OU from a tree view, and to enter the hostname for this computer.
#----------------------------------------------
# Import the Form-Drawing Assemblies
#----------------------------------------------
[void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
#----------------------------------------------
# When the form first gets loaded, this section runs.
#----------------------------------------------
$FormEvent_Load={
# Get the current domain. We'll be using the "Active Directory Services Interface" [adsi], which, unlike the ActiveDirectory module, doesn't require anything to be installed, to get AD info.
$DomainDN = $(([adsisearcher]"").Searchroot.path)
Write-Verbose("DomainDN = $DomainDN") # $DomainDN should look something like "LDAP://DC=acu,DC=local".
# Create an object "DirectoryEntry", specifying the domain, username and password.
$Domain = New-Object `
-TypeName System.DirectoryServices.DirectoryEntry `
-ArgumentList $DomainDN,
$($Credential.UserName),
$($Credential.GetNetworkCredential().password)
Write-Verbose("Domain = $Domain") # $Domain should look something like "System.DirectoryServices.DirectoryEntry".
Write-Verbose("$($Domain | Get-Member)") # Should generate several lines of Active Directory-type stuff.
$nodeName=$Domain.Name # Get the Common Name info about our AD (in our case, this gets the "acu" part of "acu.local").
Write-Verbose("Common Name (CN) = $nodeName") # Should look like "acu".
$key="LDAP://$($Domain.distinguishedName)" # Stores in $key the DN (Distinguished Name) of domain and tacks "LDAP://" to the front.
Write-Verbose("key = $key") # Should look like "LDAP://DC=acu,DC=local".
$treeview.Nodes.Add($key,$nodeName) # Add to the tree view on the form/window the name of the node. The form is now loaded, with "acu" showing.
}
#----------------------------------------------
# Define what happens when the mouse is clicked on a tree node in the $treeview.
# (It displays the OU and path to that OU in the appropriate fields, and expands the node if more nodes available.)
#----------------------------------------------
$treeview_NodeMouseClick=[System.Windows.Forms.TreeNodeMouseClickEventHandler]{
# Uncomment these lines for trouble-shooting.
Write-Verbose ($_) # The ClickEventHandler puts the clicked-event args in "$_" (i.e., "Systems.Windows.Forms.TreeNodeMouseclickEventArgs").
Write-Verbose ("Node: $($_.Node)") # The [EventArgs].Node contains the node name that was clicked on (e.g., "acu" or "MathDept", or "Cafeteria").
Write-Verbose ("Members", $($_.Node | Get-Member))
Write-Verbose ("Node.Name:", $_.Node.Name) # The Node.Name contains the path in AD to that node (e.g., "LDAP://OU=Computers,OU=Online Programs,OU=Departments,DC=acu,DC=local").
Write-Verbose ("Full Path: ", $_.Node.FullPath)
# Get the AD OrgUnit object at the Node.Name path, using the ADSI method, and store it in "$thisOU".
$thisOU=[adsi]$_.Node.Name
# Fill the form fields
$nodename.Text = $thisOU.Name # Put the name of the selected OU in the "$nodename" field on the form.
$nodepath.Text=$thisOU.DistinguishedName # Put the DN in the "$nodepath" field on the form.
# Prep next level down of the tree
$searcher=[adsisearcher]'objectClass=organizationalunit' # Prepare to search for OU objects in AD.
$searcher.SearchRoot=$_.Node.Name # Move the search pointer to the currently-selected node level.
$searcher.SearchScope='OneLevel' # Prep to search only one level deep from here.
$searcher.PropertiesToLoad.Add('name') # And to list the results as the name only.
$OUs=$searcher.Findall() # Go do the search.
foreach($ou in $OUs) { # For each item found in the search ...
$_.Node.Nodes.Add($ou.Path,$ou.Properties['name']) # Add this node-level to the treeview.
}
# Now that we have the nodes of the next level in the treeview, show them (unless we're going up).
if ($_.Node.IsExpanded) { # We've been at this level before, so we must now be going up.
$_.Node.Collapse()
} else {
$_.Node.Expand() # Expand the current node.
}
} # end of $treeview_NodeMouseClick
#----------------------------------------------
# Define Window and its Objects
#----------------------------------------------
[System.Windows.Forms.Application]::EnableVisualStyles()
# The basic window (form).
$form = New-Object 'System.Windows.Forms.Form'
$form.SuspendLayout() # Don't draw the form yet, until we have all the pieces in place (to avoid flickers, and to save calculation time, etc).
$form.ClientSize = '692, 389'
$form.FormBorderStyle = 'FixedDialog'
$form.MaximizeBox = $False
$form.MinimizeBox = $False
$form.Name = "form"
$form.StartPosition = 'CenterScreen'
$form.Text = "Select Org Unit..."
$form.add_Load($FormEvent_Load)
# Field for the tree/folder view of the Active Directory domain, and its label.
$treeview = New-Object 'System.Windows.Forms.TreeView'
$treeview.Location = '12, 20'
$treeview.Name = "treeview"
$treeview.Size = '200, 334'
$treeview.TabIndex = 4
$treeview.add_NodeMouseClick($treeview_NodeMouseClick)
$form.Controls.Add($treeview)
# the Label
$label_treeview = New-Object 'System.Windows.Forms.Label'
$label_treeview.Location = '12, 5'
$label_treeview.Size = '200, 20'
$label_treeview.Text = "Select desired Organizational Unit:"
$form.Controls.Add($label_treeview) # Add the tree view to the main window form.
# Field for the computer's Hostname, and its label.
$hostname = New-Object 'System.Windows.Forms.TextBox'
$hostname.Location = '423, 200'
$hostname.Text = $default_Hostname
$hostname.Focus()
$hostname.SelectionStart = 0
$hostname.SelectionLength = $hostname.TextLength
$hostname.Name = "hostname"
$hostname.Size = '237, 20'
$hostname.TabIndex = 1
$form.Controls.Add($hostname)
# the Label
$label_hostname = New-Object 'System.Windows.Forms.Label'
$label_hostname.Location = '237, 160'
$label_hostname.Size = '423, 20'
$label_hostname.Text = "Enter desired name (`"Hostname`") for computer:"
$form.Controls.Add($label_hostname)
# Field for the end node name, and its label.
$nodename = New-Object 'System.Windows.Forms.TextBox'
$nodename.Location = '237, 20'
$nodename.Text = $default_Selected_OU
$nodename.Name = "nodename"
$nodename.Size = '423, 20'
$nodename.TabIndex = 4
$form.Controls.Add($nodename)
# the Label
$label_nodename = New-Object 'System.Windows.Forms.Label'
$label_nodename.Location = '237, 5'
$label_nodename.Size = '423, 20'
$label_nodename.Text = "User-selected End Node:"
$form.Controls.Add($label_nodename)
# Field for the node path, and its label.
$nodepath = New-Object 'System.Windows.Forms.TextBox'
$nodepath.Location = '237, 68'
$nodepath.Text = $default_Selected_OU_Path
$nodepath.Name = "nodepath"
$nodepath.Size = '423, 20'
$nodepath.TabIndex = 5
$form.Controls.Add($nodepath)
# the Label
$label_nodepath = New-Object 'System.Windows.Forms.Label'
$label_nodepath.Location = '237, 52'
$label_nodepath.Size = '423, 20'
$label_nodepath.Text = "User-selected Org Unit Path:"
$form.Controls.Add($label_nodepath)
# The OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button'
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = "Ok"
$buttonOK.Location = '565, 334'
$buttonOK.Name = "buttonOK"
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 2
$buttonOK.Text = "OK"
$buttonOK.UseVisualStyleBackColor = $True
$form.Controls.Add($buttonOK)
$form.AcceptButton = $buttonOK # When the OK button is pressed, the form is accepted as complete, and returns from this function.
$form.ResumeLayout() # Now draw the form.
# I don't know what this does.
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
#Save the initial state of the form
# $InitialFormWindowState = $form.WindowState
#Init the OnLoad event to correct the initial state of the form
# $form.add_Load($Form_StateCorrection_Load)
#Clean up the control events
# $form.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
# return $form.ShowDialog() # This returns OK or Cancel.
$OK_or_CANCEL = $form.ShowDialog() # This shows the form, and returns the result ("OK" if OK button pressed; "Cancel" if "X" is used to close the form).
# Define object in which to return results
$Results = [pscustomobject]@{
# OK_or_CANCEL = $OK_or_CANCEL
Hostname = $hostname.Text
OU_Node = $nodename.Text
OU_Path = $nodepath.Text
}
return $Results
} # end of the "Get-Data_From_Prompt" function.
function Write-Results_To_ConfigDirFile ($Results) {
log("Writing the results to the logfile:`n`tHostname:$($Results.Hostname)`n`tOU Node:$($Results.OU_Node)`n`tOU Path:$($Results.OU_Path)")
log("Writing the results to the `"$($CONFIG_DIR)$($CONFIG_FILE)`" file.")
Add-Content $CONFIG_DIR$CONFIG_FILE "Hostname:$($Results.Hostname)"
Add-Content $CONFIG_DIR$CONFIG_FILE "OU Node:$($Results.OU_Node)"
Add-Content $CONFIG_DIR$CONFIG_FILE "OU Path:$($Results.OU_Path)"
} # end of the Write-Results_To_ConfigDirFile function
#----------------------------------------------
# END OF FUNCTIONS, ETC
#----------------------------------------------
#----------------------------------------------
# MAIN PROGRAM
#----------------------------------------------
$VerbosePreference = "Continue" # Turn on verbose Write statements for debugging.
#$VerbosePreference = "Silently Continue" # Turn off verbose Write statements.
# Create the config directory and config file.
Create-ConfigDirFile
# Try to get preferred Active Directory Organizational Unit and Computer Name from a Google spreadsheet (this step will likely rarely be used).
log("Searching Google Doc `"$($Google_Doc_Data_Filename)`" .csv for needed info.")
$Results = Get-Data_From_File
# If that doesn't work, then prompt the user for that information.
If (!($Results.Hostname)) {
log("Information not found in Google Doc; prompting the user for the needed info instead.")
$Results = Get-Data_From_Prompt
}
# Write the OU and Computer Name to a config file on the hard drive for later use.
Write-Results_To_ConfigDirFile($Results)
log("===================== END =======================`n`n`n")
$VerbosePreference = "Silently Continue" # Reset preference to default.
# END OF MAIN
</small></pre>
-->Kent Westhttp://www.blogger.com/profile/04419374220761564120noreply@blogger.com0