A little while ago I had a dilemma brought to me at work from an application team. One of the tings we have fought using the Dragon Naturally Speaking application is the loss of data or profile corruption due to the application not being properly shut down.
The solution provided to us by a third party vendor was bloated and seemed much more complex than it needed to be. An AutoIT script was written by someone in my company to use the files provided by the vendor. It was the AutoIT script that called a batch file, which called cscript to run a vbs file (which did the saving), then went back to the AutoIT to call a com file which called an executable (created by one of the vendors) to close Dragon. Did you get lost? I was for a while trying to figure out what the scripts were doing and why.
It’s a simple process and the execution should be simple as well (although in IT that is often not the case). So, I set out to find a way to do it. Upon inspecting the COM I noticed it pointed to DragonCloser.exe which appeared to be a custom executable that the third party vendor wrote (or maybe Nuance did…). So, there must be an API to use. I looked it up and found an SDK online but I was hesitant to use it because one of my goals was not to have to push additional files to all of the PC’s that use Dragon. I also didn’t want to modify the scripted package I wrote to install Dragon.
After taking another look at the VBS script I noticed it was creating an object without ever loading an assembly or any library of any sort. This was the clue I needed to know that all I really needed was already loaded on the PC’s. I dove into the Program Files directory and found Interop.DNSTools.dll. Interop obviously standing for Interoperability and that was exactly what I was looking to do. I opened up the library in visual studio and browsed for the function I found in the VBS script. Now I knew what I needed to do.
The resulting Powershell script is only 7 lines. You can view/download it here: SaveAndCloseDragon
It’s fully commented and shouldn’t be too hard to follow. I hope this helps anyone looking to solve this issue. You can set it as a logoff script on any computer running Windows Vista or higher. I’m not sure if XP can do Powershell scripts or if installing Powershell adds that functionality to the logoff scripts area.
Wow, it’s been 5 months since I posted here! A lot has been going on with work, I took a real vacation, and I’m not so single anymore. I definitely have a ton of material to blog about, especially in the Powershell realm (go figure…). Like the script I wrote that will do subnet discovery (IP, PC name, logged in user) with just the input of a single PC. I’ve also been meaning to post stuff I wrote much longer ago like how I do my logging, how I put everything together in a single console, and maybe even a fun one: “How to send a speech message to another Windows 7 machine :).” I feel a little bad though… I haven’t touched PS much at all the past few months due to projects at work not leaving me time to play with it as much since they have me going into other realms like Citrix.
Today, I’m going to talk about a little PHP I wrote from scratch to dynamically create a photo gallery on a web page. I’m building one for my photography and I want the site to be LOW MAINTENANCE! This means no having to jump into code every time I want to add something or update it. The only other time I’ve dabbled in PHP is when I created this blog’s theme from absolute scratch. The code for the image gallery is simple, and I’m sure I could have googled for some code, but I would be much of a Curious Geek if I didn’t try and figure it out on my own. Since I’m not really a PHP programmer and I don’t know much, I just googled on how to do specific tasks like “PHP list directory” or “PHP dynamically insert html into page.” Found the functions I needed pretty quick and as a result I have this:
<?php $dirlist = scandir('./Portraits'); ?>
This is the third line of code, below the DOCTYPE and my PHP include (my header for all of my pages). This is the line of code that actually scans a given directory (In this case, the directory “Portraits” inside the same directory as the php page itself, they both are in the root). If I were putting the PHP file in the same directory as the photos, it would just be a “.”. The end of that sentence looks weird…. I assign it to the variable $dirlist so I can reference it later when I’m actually inserting the html.
I guess I should explain the layout of my gallery. I have the main photo being viewed on the left, and the thumbnails of all the images in the directory on the right of that. My rows are only 3 thumbnails wide. I use CSS to set the sizes and layout, which I wont get into here.
Next, we have:
<div class="photoBox" id="photo1"> <img id="displayImage" src="./Portraits/<?php echo $dirlist[2] ?>"> <div id="displayCaption" class="caption captionFade"></div> </div>
This is the div that contains the displayed photo. The only thing I really want to note here is the insertion of php code into the img src URL. I want it to start out displaying the first image it finds (the first two values in the array are . and ..).
Now here is the code doing the brunt of the work:
<div class="thumbContainer"> <?php foreach ($dirlist as $imgname) { If ($imgname !== "." and $imgname !== ".." and strpos($imgname, 'txt') == false) { $filename = substr_replace($manipulate, "txt", -3, 3); $filepath = './Portraits/' . $filename; If (file_exists($filepath)) { $innerHTML = file_get_contents($filepath); } Else { $innerHTML = " "; } $htmlString = '<div class="thumb"><img src="./Portraits/' . $imgname . '" onmousedown="document.getElementById(\'displayImage\').src=\'./Portraits/' . $imgname . '\'; displayCaption.innerHTML=\'' . $innerHTML . '\'"></div>'; echo $htmlString; } } ?> </div>
This is the container for the thumbnails, where the end-user clicks images to dynamically change the page.
Pretty simple stuff I think. It didn’t take me long to write it (if you subtract the time it took me to get quotes and php syntax all fixed…). This way, when I want to add an image and optionally a caption all I have to do is use FTP to upload them to the proper directory… AND THAT’S IT!! No having to jump into code for every little update!
Thanks to the official PHP 5 manual for telling me how to use a couple functions 🙂
EDIT: This is my old, kludgey way of doing it. Please see this new post: Remotely Enabling WinRM – Improved!
*********************
This post is going to build off of the technique I used in my previous post. My company only recently deployed Powershell to all of our XP devices. But neither the XP nor Windows 7 have WinRM enabled by default. This limits me so I had to seek a way to enable it on the fly. In the past when I’ve needed to run commands that weren’t “remote-friendly” in Powershell I had to create a scheduled task on the PC to run a batch script. I figured that was the way I’d need to go with this so I set out to do it. The tricky part was getting Powershell to run elevated on the Win 7 machines, running as System does not necessarily do that. With the help of the last post’s discovery I was able to accomplish it, though. Now I can remotely gather data without interfering with the user! They say the perfect IT guy is the one that accomplishes his work without the end-user even knowing what’s going on.
I’ve decided to just start attaching my completed script file to make it easier, and it’s commented so it should explain what I’m doing well enough. I’ve also thrown my info header at the top of the script so that maybe I can get some exposure if these things get passed around. This script requires that you have administrative rights on the remote machine!
In short, what it does is write .bat files to the PC, sets scheduled tasks to run them, runs them manually, and then deletes the .bat files and tasks.
I also want to note the reason I have -skipnetworkprofilecheck added to the bat file inside the script. This can raise a security concern since it’s going to enable the firewall rule for not only domain and private networks but also public! If I don’t do this, enable-psremoting will fail on some of our PC’s that run virtual environments such as VMWare (which my group, and some others, use for testing). The VMWare network adapter (and probably VirtualBox and others are like this) are set as public networks. If enable-psremoting detects a public network it will NOT do the enabling, even with the -force command. You’ll see later on in the .ps1 script where I modify the firewall rule to remove public networks.
Ok, here is the script: EnablePSRemoting
**I haven’t tested this actual script. The one I use at work is integrated into my console and some of the code in this is actually separate functions in my console. I’m pretty sure it works though 😉
I searched far and wide over the internet looking for an easy way to elevate powershell in a script whenever I needed to. The only solutions I ever found were practically a full page of code to look at this, and check that, then do this, jump through this hoop so it could do that… It had to be simpler than that! I didn’t even attempt to use their solutions because I refused to accept it’s complicated code.
It happens with start-process. The parameters are picky in how they are used but once you straighten it out in your head (or through many different trial and error iterations like myself) it’s not too bad. You can eleveate from within powershell, from a command prompt, or from a shortcut.
From Powershell:
start-process powershell.exe -verb runAs
Or if you want to run a command:
start-process powershell “get-process” -verb runAs
Anything passed in the quotes after the process name is passed as an argument to powershell (kind of like a script block)
You can also pass the path to a script as the argument.
From the Command Line
powershell.exe “start-process powershell -verb runAs”
Same thing as in powershell except you start a normal shell and execute a command to open an elevated shell right off the bat.
From a Shortcut
Your target should be: C:\path-to-powershell\powershell.exe “optional script file path” -verb runAs
And that’s all there is to it. Now, if you have UAC turned on you will be prompted. At work I don’t have this problem because we have a man-in-the-middle software that injects the token that allows the elevation without a UAC prompt.
Finally got tired of all the spam that shows up in my comments awaiting approval (all the activity I get, lol) so I implemented a plugin to to make sure the commenter is human.
Simple enough.
A couple of days after I made my last post I realized that, for that specific purpose, I could have just dynamically created hashtables as the values of each array. It would look like this pseudo-code:
$array = @() $array += @{ DriveLetter = $object.DriveLetter TotalSpace = $object.Size FreeSpace = $object.FreeSpace }
Just remember to first declare the array variable as an array or you’ll get errors. Then as I enumerate through each returned object it’s as simple as:
ForEach ($disk in $array) { write-host "Drive: $disk.DriveLetter has $disk.FreeSpace free space out of a total of $disk.TotalSpace" }
Or whatever I wanted to do with it. Now that I think about it, creating a specific name for each variable isn’t necessary and I can’t think of any scenarios where I really need to do that. I guess it could happen, though, so I’ll leave my previous post up.
I was building a more involved function the past couple of days for gathering data from a PC in the enterprise should troubleshooting be necessary. The type of company I work for doesn’t always allow us to stay on the PC and troubleshoot an item (unless it’s a total blocker) so being able to gather a broad range of data is necessary to really troubleshoot since we can’t be on the machine. Part of that function is retrieving hard drive information. I’m only gathering a few pieces of info, but since PC’s can have multiple drives I needed a good way to manage each disk separately. I could have done it with the following line of code, but as you can see from the screenshot, it’s messy and the numbers output are in bytes which isn’t useful at first glance.
gwmi -class win32_logicaldisk | Where {$_.DriveType -eq 3 -OR $_.DriveType -eq 4} | select -property DeviceID,Size,FreeSpace
The big thing is to convert those numbers into something more meaningful. I also don’t like the clumped output and wanted to be able to handle each disk separately and call any of it’s properties without additional formatting (to see what I mean, run that code and then try pulling just $var.DeviceID and you’ll see all the hashtable @ and{} characters… it’s a mess). I decided to create the hashtable myself. Since there could be multiple disks I also needed it to really be more like an object. That’s where nesting my hashtables came in. My other requirement, dynamically creating variables, comes from wanting to return all of my unknown-beforehand-number-of-disks within the same variable, but needing a way to make unique hashtable key names ($return.Disk1… , $return.Disk2… , etc). It sounds confusing, so here’s the code:
Function getHD($strComputer) { # Retrieves hard drive information $return = @{} # declare our return variable as a hashtable so it doesn't get created as a string $i = 0 # counter and dynamic part of variable for disks $hd = gwmi -comp $strComputer -class win32_logicaldisk ForEach ($disk in $hd) { If ($disk.Drivetype -eq 3 -OR $disk.DriveType -eq 4) { # only return physical hard drive and network drive types (5 is CD) $i++ $return.numDrives = $i # where I store the number of drives found so I can use that to iterate through them all later $return += @{ "Disk$i" = @{ # new hashname must be in quotes so variable will be expanded DriveLetter = $disk.DeviceID Size = $disk.Size/1048576 # Convert to MB FreeSpace = $disk.FreeSpace/1048576 # Convert to MB } } } } return $return }
So now what? I call my function by: $hdinfo = getHD $pc Then I run through that returned hashtable “object” and output the data to a file. That is just a few simple lines:
For ($i=1; $i -le $hdInfo.numDrives;$i++) { $hdvar = "`$hdinfo.disk$i" invoke-expression $hdvar | Out-file $global:consolePath\PCData\$pc\PCVitals.txt -append -noclobber }
And what does it all look like?
Name Value ---- ----- FreeSpace 1171586.5703125 DriveLetter C: Size 1437513.9921875 Name Value ---- ----- FreeSpace 1143174.359375 DriveLetter D: Size 1423843.99609375 Name Value ---- ----- FreeSpace 1034453.83984375 DriveLetter E: Size 1430727.99609375
I could do a little more formatting if I wanted, but this is good enough for me! And if I wanted to further use that data in a function it’s easy to reference an individual item with just $hdinfo.Disk1.FreeSpace or $hdinfo.Disk2.Size
I need to vent a little before I get into the code. Had a frustrating one today! All because of unclear documentation in MSDN’s published library of their .NET classes. I guess it’s true what they say, that sometimes you should listen to what isn’t said.
I had to build a work-around to a problem with an MSI from a certain cardiovascular imaging software. It seems the MSI wants to strip the permissions for the local “Users” group off of the C:\ProgramData folder. Not cool! At first the vendor tried to brush it under the rug and gave me a few lines about the development process, blah, blah (develoment?! you’re just fixing the installer!) and then said it could take 10-12 weeks (which is further out than our very-rushed go-live) and if I could just work around it. Did I mention this isn’t a lesson in customer service skills?
So anyway, I sat down to figure out how I was going to do it. I’ve lost a lot of my VB skill because I haven’t used it in over 6 months and I’ve been learning a lot of Powershell. I love Powershell and I’ve already written code to modify permissions (on a file on XP) so I figured this would be easy. The one good thing I had going is the issue is only affecting Windows 7 so running the Powershell script locally isn’t a problem (we never pushed PS out to our XP devices). What I thought would be a 5 minute mod of my existing code turned out to be about 30-40 minutes.
It seems just supplying the user/group (IdentityReference), read/write/etc (FileSystemRights), and Allow/Deny (AccessControlType) were enough… but not really. Mine was set to:
$Ar = New-Object system.security.accesscontrol.filesystemaccessrule("Users","ReadAndExecute","Allow")
When setting just those three properties the permissions got applied but if you right-clicked on the folder and viewed the Security tab of the folders properties you saw a blank list of rights for that user/group. All except for “Special Permissions” at the bottom. If you went into advanced and dug a little deeper you could see that the Traverse folder/ execute file, List folder/read data, Read attributes, and Read extended attributes were all selected. Read permissions was not. Kind of a problem. Below is what that looked like.
The other problem is it says “Apply to: Subfolders and files only”. With the changes not applying to the root folder I actually made the changes on, and not having proper read permissions, I have a funky looking ACL. The odd thing is that the permissions I was wanting to apply worked. Someone who fell into the “Users” group was able to read and execute like I wanted. But, it’s technically broken so it needed to be fixed.
So I called up the MSDN library to find out what else I might need. This post is already longer than it needs to be so it will suffice to say that I fiddled around with the InheritanceFlags Enumeration and PropagationFlags Enumeration parameters for quite a while. I assumed the the FileSystemRights could be comma-separated since there were many options and you could have multiple. I tried that and it worked, I got the “Read Permissions” checkbox back on the advanced properties view. Still, the ACL was blank on the Security tab (with the exception of Special Permissions). that took setting the Inheritance and Propagation flags correctly. Both of these flags apply ONLY to the child object. They do not affect whether the folder you are making changes to will inherit from it’s parent. Inherit determinse whether the child object(s) inherit from the folder you are making changes on and propagate is what it pushes them down to. Propagate is dependent on Inherit.
Ok, finish this up already. Since I needed to put permisions back to default so that All Users/Public can appropriately access that folder I needed both file and folders to inherit. And the kicker (reading what isn’t said…) is that the Propagation flags have to be set to None. Why? Because it doesn’t mean it wont propagate, it means it’s going to use the default value for propagation, which is: This folder, subfolders, and files! But the documentation does not say that anywhere! It finally dawned on me that, that is what it would do. So, my final code for adding “Users” to the ACL for the C:\ProgramData folder with Read and execute, List folder contents, and Read permissions is:
$Acl = Get-Acl C:\ProgramData $Ar = New-Object system.security.accesscontrol.filesystemaccessrule("Users","ReadAndExecute,Read","ObjectInherit,ContainerInherit","None","Allow") $Acl.SetAccessRule($Ar) Set-Acl C:\ProgramData $Acl
Line 1 I’m getting the ACL of the object I want to modify
Line 2 I’m creating a new access rule and setting all of the parameters. The local Users group, the TWO FileSystemRights, the Inheritance Flags (so that folders and files inherit), the Propagation flag (none means use default), and finally that I”m Allowing access and not Denying.
Line 3 I’m taking that access rule and adding it to the ACL object I created from the folder
Line 4 I’m actually setting the new ACL [in stone]
Yes, all that for 4 lines of code.
“Special” thanks to the MSDN library: http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemaccessrule.aspx
Bleh! I’ve been so busy with the new position I started in April I’ve fallen off posting on this blog. I have a lot of Powershell stuff I want to post—creating progress/activity bars, hashing, and perhaps a series on creating a simple text console. I’ve also not been paying attention to replies, sorry! I get so much spam from this blog that Gmail started dumping all my email alerts to comments in my spam box, which I never check. I did respond to a couple, though… and realized I REALLY need to build-out the styling of the reply section and add the functionality to reply to comments!
Anyway, I just wanted to drop this quick and easy one here that I had to figure out last night. I tried googling a solution for batch conversion last night when I needed to convert about 40 flv’s to mp4. The web yielded nothing too great. One site had a Powershell script but it didn’t work. So, I just took from it what parameters I had to pass to the VLC application and wrote my own script.
This script has to be run from the directory where the videos are and it outputs them to the same directory. I’m using Powershell 2.0 on Windows 7 Ultimate with VLC 2.0.4
$fileItems = Get-childitem -filter *.flv
ForEach ($file in $fileItems) {
$destination = “$($file.fullname).mp4”
start-process “C:\Program Files (x86)\VideoLAN\VLC\vlc.exe” “-I dummy -vvv “”$($file.fullname)”” `
–sout=#transcode{vcodec=h264,vb=1024,acodec=mpga,ab=192,channels=2,deinterlace}:standard{access=file,mux=ts,dst=””$destination””} vlc://quit” -wait
}
The parts:
Hope this saves someone some time 🙂