Also See...

You should also check out my non-technology blog at:

http://www.bobsbasement.net/

Still More Examples of Bad Technical Support

Here is another entry in my on-going list of examples for both good and bad technical support experiences. For this situation, I will show an example of bad technical support as provided by the Microsoft Outlook Support Team.

Here is the scenario: about two months ago one of my Hotmail accounts stopped working with Windows Live Mail. When I attempted to sync that particular email account, I received an error message that was similar to the following example:

Bear in mind, however, that I made no changes to my Windows Live Mail settings, and I have several other Hotmail accounts which are working perfectly with the same settings on the same computer.

So I started a support issue with the the Microsoft Outlook Support Team, and over the next several weeks I went back and forth with several people on that team who were clearly uninterested in working on this issue. (In fact, at one point they simply closed the case without notifying me!) As of today the issue is still not resolved, and I have not heard from anyone at Microsoft within the past month.

As I said before, this is an example of really bad technical support.

With that in mind, listed below is the full thread from my support case in order of the events. Note that I have substituted the following variables where necessary to hide my actual account name, email address, and support number:

  • %ACCOUNT_NAME% is my Hotmail account name
  • %EMAIL_ACCOUNT% is my email address for my Hotmail account
  • %TICKET_NUMBER% is my ticket number for my Microsoft support case (e.g. SRX12345678ID)

So without further narrative, here are all of the notes from the support case.

04/18/2015 15:14

Service: General Issue

What type of problem do you have?

  • Selected Product
    • Outlook
  • Selected Issue
    • Your account has been temporarily blocked

Enter a short description of your issue: Cannot Sync Email from Windows Live Mail

Enter the email address (Microsoft account) affected by this issue: %EMAIL_ACCOUNT%

Enter your contact email address: %EMAIL_ACCOUNT%

Enter a detailed description of your issue:

I was able to sync my %EMAIL_ACCOUNT% account in Windows Live Mail until a week or so ago, and now I receive the following error message:

Unable to send or receive messages for the Hotmail (%ACCOUNT_NAME%) account.
Server Error: 3219
Server: 'https://mail.services.live.com/DeltaSync_v2.0.0/Sync.aspx'
Windows Live Mail Error ID: 0x8DE00005


For reasons that are completely unrelated to this issue, I completely reinstalled Windows 7 Ultimate on this computer, and yet after reinstalling Windows and Windows Live Mail on this computer I still see the same error. In addition, I have a couple of other Hotmail accounts that I use with Windows Live Mail on the same computer and they work without any errors.

With that in mind, the problem should not be caused by any settings on my computer. However, I did not make any changes to my account settings at Hotmail before I started seeing this problem.

There is one thing that I have noticed, though - when I log into the Hotmail.com website, when I use one of my other Hotmail accounts the title bar says Outlook.com, whereas the title bar for my %EMAIL_ACCOUNT% account says Outlook Mail (Preview).

04/18/2015 15:18

From: Microsoft Support

We are writing to let you know that we have received your request for support and will reply within 24 hours.

If you don’t see a message from us within 24 hours, check to see if email from @css.one.microsoft.com is in your junk mail folder. You can check the status of your request on the Microsoft Support site.

https://support.microsoft.com/oas/default.aspx?tp=il&tenant=WOL&sd=winlive&incno=%TICKET_NUMBER%

Thank you,
The Microsoft Support team

04/19/2015 22:51

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hi,

Thank you for contacting Outlook.com support.

My name is Therese and I’m glad you have reached us today. I have read your description and I understand that you are getting an error message when syncing your account at Windows Live Mail. Let me see what I can do for you.

To start off, we appreciate your efforts in isolating the case by uninstalling and reinstalling your Operating System and Windows Live Mail on your computer. You also mentioned that other Microsoft accounts are working fine on Windows Live Mail which tells us that this is not an issue with the mail client but can be an account-related issue.

With that, I would like you to know that I have escalated this case to our Support Specialist for further evaluation. We will get back to you as soon as we have a resolution or if we need more information.

Your patience is highly appreciated.

Regards,
Therese

04/21/2015 11:12

Subject: RE: %TICKET_NUMBER%
From: %ACCOUNT_NAME%

Has there been any progress on this issue?

FWIW - I have included a screen shot of the error message in Windows Live Mail.

Thanks.
--------------------
Attachments:
0x8DE00005.png

04/21/2015 13:17

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hello,

Good to hear from you again. This is John from Outlook.com support.

We appreciate the screenshot that you have provided us. I have checked your service request and found out that your issue is still being handled by our Support specialists and the investigation is still ongoing. Once we got any developments or if there is anything that our Support specialist would need from you, we'll inform you right away.

Thank you for your cooperation and patience on this matter.

Regards,
John

PS: Please do not delete the first email notification from us. If you did not receive another notification in 24 hours, please check the link on the first email notification to see the case progress.

04/25/2015 12:33

Subject: %TICKET_NUMBER% (Reopen)
From: %ACCOUNT_NAME%

It has been a week since I first opened this support case, and from what I could tell - nothing was ever done. So imagine my surprise when I logged in today and I discovered that this support case had been closed without a single notification from Microsoft. This is terrible customer service, and I AM A VERY DISSATISFIED CUSTOMER!!!

At this point I expect three things:

1. I demand that this issue be escalated to someone who can actually work on the issue and resolve it; I no longer want to suffer through working with incompetent 1st-tier support people.

2. I expect to hear from someone within the next 24 hours with an actual update on this issue; I do not want to hear another blasé response which says "the investigation is still ongoing," because it very clearly was not being investigated.

3. I expect to hear from a manager within the next 48 hours as to why this issue was never worked, never resolved, and closed without contacting me.

If I do not hear from anyone within the times that I have put forth, I will escalate this issue through appropriate channels on my own, which will reflect even more poorly on those who have been doing a terrible job thus far.

04/25/2015 16:52

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hi %ACCOUNT_NAME%,

Thank you for your response and for sharing your feelings with us. I’m Froilan, at your service.

We apologize for taking your valuable time in trying to resolve your issue with the error message when syncing your account in Windows Live mail. I fully understand how you feel about the inconvenience this has brought you. Please be informed that I will get back to our Support Specialist to get update about the status of your case. We understand the urgency of this issue; however, we cannot provide you with the time when a reply will be available. Rest assured that this post is being tracked and we will inform you immediately once we get any updates from them.

We appreciate your continued patience in going through this issue.

IMPORTANT REMINDERS:

Please save and keep the first email notification you received after submitting this request. That notification contains the link for this service request. If you did not receive another notification in 24 hours after your reply, you can check the status of this case and view our response by clicking the link on the first notification.

Best regards.

05/01/2015 09:54

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hello %ACCOUNT_NAME%,

How is it going? We received an update coming from our Escalations Engineer and based from what we've observed, it seems the issue is encountered when the server settings for IMAP is not selected.

What you need to do is to make sure that you've selected and followed the Manual Server Settings for IMAP. This should allow the syncing of your accounts.

For more information about this setting, please refer to the IMAP setting that can be found in the link below.

http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app

Kindly share us the outcome afterwards.

Regards,
Microsoft Support Team

05/03/2015 22:22

Subject: RE: %TICKET_NUMBER%
From: %ACCOUNT_NAME%

This simply does not make sense; I have several other Hotmail accounts which I simply add them to Windows Live Mail and they automatically work. In the Properties dialog for each of my other accounts, the Server Information tab lists "My mail server is an HTTP server" with the following URL:

https://mail.services.live.com/DeltaSync_v2.0.0/sync.aspx

These are the EXACT same settings as the %EMAIL_ACCOUNT% email account. So why would this one account require custom IMAP settings?

Also, this account was working fine until a few weeks ago when I started seeing the errors. I made no changes to my settings, so it would seem that Microsoft has changed something about this specific Hotmail account. As I pointed out before, the web page banner when I am using this account reads "Outlook Mail (Preview)", which leads me to believe that this account was only partially migrated to the new Outlook Mail servers. With that in mind, I would theorize that the problem is due to a failed attempt on Microsoft's behalf when trying migrate my account from the old Hotmail.com servers to the new Outlook.com servers.

05/03/2015 22:23

Subject: %TICKET_NUMBER%
From: %ACCOUNT_NAME%

By the way, while I sincerely appreciate the assistance of the escalation engineer, I still have not heard back from a manager with an answer as to why my support case was closed by the initial support engineer after no work was completed and no attempts were made to contact me.

05/03/2015 23:56

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hi %ACCOUNT_NAME%,

Thank you for providing us additional details. I’m Hyver from Outlook.com Support and I have reviewed the exchange of responses that you had with my colleagues. I’m truly sorry if the issue still persists after a week of communicating with us. I understand the displeasure that you’ve expressed for the support that you experienced. We’re still here to help you on your issue.

I really apologize for the inconvenience that you’ve faced. I know you’re expecting an email from a manager explaining why the support ticket was closed without someone trying to attempt to contact you. I'd like to let you k now that we are actively monitoring cases that are being reviewed by the Escalations Team and we do not close cases with open escalations. So we would like to ask for a copy of the email notification that indicates that the case was closed. I will include this to my reports as this could be an honest mistake or a system glitch,

I totally recognize that you deserve a valid explanation about what happened and we are not letting our customers to be upset at any point of our investigation. We don't like to see our customers upset and inconvenienced and we always strive to create a positive customer experience. With that being said, this should be reviewed again by our Escalations Team and should be taken care with utmost urgency. Please do not lost faith in us as we are doing our best to resolve this issue the soonest possible time.

As we speak, this support request is in our Escalations Team queue and should be treated with immediate attention. We appreciate your cooperation and understanding.

Should you have other questions or clarifications, please do not hesitate to contact us back.

Sincerely,
Hyver

05/05/2015 18:04

Subject: RE: %TICKET_NUMBER%
From: %ACCOUNT_NAME%

Hello Hyver,

There was never an email when the case was closed, which was part of my complaint. If you look at the case notes, you will see that I had to reopen the case on 04/25/2105 when I discovered that the case had been closed when I logged into this support website to check on the status.

05/05/2015 19:47

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hello,

Thank you for letting us know that there was no email with regard to this case that was closed. We will report this to our higher support team and we'll get back to you immediately once we have the latest update from them.

Again, your patience and understanding are much appreciated.

Kindest regards,
Glenda

05/12/2015 22:46

Subject: %TICKET_NUMBER%
From: %ACCOUNT_NAME%

Another week has passed by, and yet I still have not heard anything, which leads me to believe that no one at Microsoft is actually doing nothing with this case. From my perspective, it seems that the "Escalation Agents" with whom I am working are simply stalling for time and hoping that either the problem will go away or I will quit trying and give up.

With that in mind, I will keep everyone up-to-date: the problem is still there - which should be expected since no one at Microsoft appears to be doing anything to fix the problem.

So let me recap the past week or so: I have heard nothing from Microsoft since the last time that I requested an update, my issue is not resolved, and I never heard from a manager at Microsoft with an explanation as to why the front-line engineers closed the case without contacting me or to discuss a plan of action to resolve this issue.

All of this reflects very poorly on the support engineers with whom I am working. As such, I want to work with someone else. Someone who knows what they're doing. Someone who can actually resolve an issue.

05/13/2015 03:37

Subject: %TICKET_NUMBER%
From: Escalation Agent

Note: To check the status of your request, kindly refrain from deleting our email notification. If you haven't received another notification in 24 hours, you can view the agent's response on URL link provided on the notification email.

Hi,

This is Audrey and we apologizer if this case has been going on for so long without any results. Upon checking your concern is still under investigation by our support specialist. We will be making a follow up regarding this and once we have received a word from them it will be immediately relayed to you.

Rest assured that we are exhausting our resources to resolve this matter as soon as possible.

Thank you,
Outlook.com Support Team

05/19/2015 13:36

Subject: %TICKET_NUMBER%
From: Escalation Agent

Hello %ACCOUNT_NAME%,

Sorry for the wait. We received an update coming from our Escalations Team and there's a possibility that your account's server settings were changed. With this, configuring your account to Windows Live Mail using this server "Windows Live Hotmail" will not work.

We are still working on your issue but what you can do is to use IMAP as your server type if you want to sync your emails.

Thank you for consideration and cooperation.

Regards,
Microsoft Support Team

05/29/2015 13:19

Subject: RE: %TICKET_NUMBER%
From: %ACCOUNT_NAME%

I do not wish to use IMAP; I have used that before and it has presented me with a separate series of issues. I will continue to wait for you to fix the problem with the server settings.


To be honest, at this point I have little faith that the Microsoft Outlook Support Team will do anything to resolve this issue. It is abundantly clear to me that the support team is ignoring my support case and simply waiting for me to close it, which I refuse to do since my problem is not resolved.

So once again I reiterate the obvious - this is a perfect example of truly awful technical support, and it demonstrates why many people eventually give up on Hotmail and switch to Gmail.

Posted: Jun 25 2015, 15:44 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Microsoft | Support | Windows
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Simple Java Wrapper Class for raspistill on the Raspberry Pi 2

Like many self-proclaimed geeks, I can't resist a change to play with new technology. So when the Raspberry Pi 2 was released a short time ago, I didn't hesitate buying one. My first impressions was: for $35, that's a lot of computer power in a very small package. And because I really am a geek, I will admit that the first thing I tried to do with it was to install DOSBOX and run Microsoft Flight Simulator 4...

FS4

Once I got that out of my system, I started experimenting with some simple Java programming and the Raspberry Pi camera module. (It's great that Java is built-into Raspian Wheezy; in the past you had to install it.)

That being said, there isn't any direct I/O control of the camera via Java, although that's pretty much expected. When considering ways to control the camera I came across the Pi4J library, which provides Java-based APIs to control the Raspberry Pi's I/O. This looks like it will be great for me eventually, but for the moment the best idea that I could come up with was to write a simple Java-based wrapper class for the raspistill command line executable.

With that in mind, here's a sample class that you can use to take photos from Java on a Raspberry Pi. I added lots of code comments to explain how everything works, and I'll provide a little more detail after the code sample.

// This class is a very simple Java wrapper for the raspistill executable,
// which makes it easier to take photos from a Java application. Note that
// there are considerably more parameters available for raspistill which
// could be added to this class. (e.g. Shutter Speed, ISO, AWB, etc.) 

public class RaspiStill
{
   // Define the path to the raspistill executable.
   private final String _raspistillPath = "/opt/vc/bin/raspistill";
   // Define the amount of time that the camera will use to take a photo.
   private final int _picTimeout = 5000;
   // Define the image quality.
   private final int _picQuality = 100;

   // Specify a default image width.
   private int _picWidth = 1024;
   // Specify a default image height.
   private int _picHeight = 768;
   // Specify a default image name.
   private String _picName = "example.jpg";
   // Specify a default image encoding.
   private String _picType = "jpg";

   // Default class constructor.
   public void RaspiStill()
   {
      // Do anything else here. For example, you could create another
      // constructor which accepts an alternate path to raspistill,
      // or defines global parameters like the image quality.
   }

   // Default method to take a photo using the private values for name/width/height.
   // Note: See the overloaded methods to override the private values.
   public void TakePicture()
   {
      try
      {
         // Determine the image type based on the file extension (or use the default).
         if (_picName.indexOf('.')!=-1) _picType = _picName.substring(_picName.lastIndexOf('.')+1);

         // Create a new string builder with the path to raspistill.
         StringBuilder sb = new StringBuilder(_raspistillPath);

         // Add parameters for no preview and burst mode.
         sb.append(" -n -bm");
         // Configure the camera timeout.
         sb.append(" -t " + _picTimeout);
         // Configure the picture width.
         sb.append(" -w " + _picWidth);
         // Configure the picture height.
         sb.append(" -h " + _picHeight);
         // Configure the picture quality.
         sb.append(" -q " + _picQuality);
         // Specify the image type.
         sb.append(" -e " + _picType);
         // Specify the name of the image.
         sb.append(" -o " + _picName);

         // Invoke raspistill to take the photo.
         Runtime.getRuntime().exec(sb.toString());
         // Pause to allow the camera time to take the photo.
         Thread.sleep(_picTimeout);
      }
      catch (Exception e)
      {
         // Exit the application with the exception's hash code.
         System.exit(e.hashCode());
      }
   }

   // Overloaded method to take a photo using specific values for the name/width/height.
   public void TakePicture(String name, int width, int height)
   {
      _picName = name;
      _picWidth = width;
      _picHeight = height;
      TakePicture();
   }

   // Overloaded method to take a photo using a specific value for the image name.
   public void TakePicture(String name)
   {
      TakePicture(name, _picWidth, _picHeight);
   }

   // Overloaded method to take a photo using specific values for width/height.
   public void TakePicture(int width, int height)
   {
      TakePicture(_picName, width, height);
   }
}

The wrapper class should be pretty self-explanatory: calling the TakePicture() method with no parameters will use the application defaults, and calling the overloaded methods will allow you to specify the output filename and the width/height for the image. You specify the image type based on the file extension; (e.g. ".jpg", ".png", ".gif"). There are a bunch of additional raspistill options which you can easily add to the application; see the Raspberry Pi camera module page for more information.

Here's a simple application to test it out:

public class CameraTest
{
  // Define the number of photos to take.
  private static final long _numberOfImages = 5;
  // Define the interval between photos.
  private static final int _delayInterval = 5000;

  public static void main(String[] args)
  {
    try
    {
      // Create a new RaspiStill object.
      RaspiStill camera = new RaspiStill();
      // Loop through the number of images to take.
      for (long i = 0; i < _numberOfImages; ++i)
      {
        // Capture the image.
        camera.TakePicture("image" + i + ".jpg",800,600);
        // Pause after each photo.
        Thread.sleep(_delayInterval);
      }
    }
    catch (Exception e)
    {
      // Exit the application with the exception's hash code.
      System.exit(e.hashCode());
    }
  }
}

Now that I've shared this odd little sample, I'll wander back to my dungeon so I can keep playing with my Raspberry Pi.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Jun 11 2015, 20:14 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Java | Raspberry PI
Tags:
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Simple Java Wrapper Class for raspistill on the Raspberry Pi 2

Like many self-proclaimed geeks, I can't resist a change to play with new technology. So when the Raspberry Pi 2 was released a short time ago, I didn't hesitate buying one. My first impressions was: for $35, that's a lot of computer power in a very small package. And because I really am a geek, I will admit that the first thing I tried to do with it was to install DOSBOX and run Microsoft Flight Simulator 4...

FS4

Once I got that out of my system, I started experimenting with some simple Java programming and the Raspberry Pi camera module. (It's great that Java is built-into Raspian Wheezy; in the past you had to install it.)

That being said, there isn't any direct I/O control of the camera via Java, although that's pretty much expected. When considering ways to control the camera I came across the Pi4J library, which provides Java-based APIs to control the Raspberry Pi's I/O. This looks like it will be great for me eventually, but for the moment the best idea that I could come up with was to write a simple Java-based wrapper class for the raspistill command line executable.

With that in mind, here's a sample class that you can use to take photos from Java on a Raspberry Pi. I added lots of code comments to explain how everything works, and I'll provide a little more detail after the code sample.

// This class is a very simple Java wrapper for the raspistill executable,
// which makes it easier to take photos from a Java application. Note that
// there are considerably more parameters available for raspistill which
// could be added to this class. (e.g. Shutter Speed, ISO, AWB, etc.) 

public class RaspiStill
{
   // Define the path to the raspistill executable.
   private final String _raspistillPath = "/opt/vc/bin/raspistill";
   // Define the amount of time that the camera will use to take a photo.
   private final int _picTimeout = 5000;
   // Define the image quality.
   private final int _picQuality = 100;

   // Specify a default image width.
   private int _picWidth = 1024;
   // Specify a default image height.
   private int _picHeight = 768;
   // Specify a default image name.
   private String _picName = "example.jpg";
   // Specify a default image encoding.
   private String _picType = "jpg";

   // Default class constructor.
   public void RaspiStill()
   {
      // Do anything else here. For example, you could create another
      // constructor which accepts an alternate path to raspistill,
      // or defines global parameters like the image quality.
   }

   // Default method to take a photo using the private values for name/width/height.
   // Note: See the overloaded methods to override the private values.
   public void TakePicture()
   {
      try
      {
         // Determine the image type based on the file extension (or use the default).
         if (_picName.indexOf('.')!=-1) _picType = _picName.substring(_picName.lastIndexOf('.')+1);

         // Create a new string builder with the path to raspistill.
         StringBuilder sb = new StringBuilder(_raspistillPath);

         // Add parameters for no preview and burst mode.
         sb.append(" -n -bm");
         // Configure the camera timeout.
         sb.append(" -t " + _picTimeout);
         // Configure the picture width.
         sb.append(" -w " + _picWidth);
         // Configure the picture height.
         sb.append(" -h " + _picHeight);
         // Configure the picture quality.
         sb.append(" -q " + _picQuality);
         // Specify the image type.
         sb.append(" -e " + _picType);
         // Specify the name of the image.
         sb.append(" -o " + _picName);

         // Invoke raspistill to take the photo.
         Runtime.getRuntime().exec(sb.toString());
         // Pause to allow the camera time to take the photo.
         Thread.sleep(_picTimeout);
      }
      catch (Exception e)
      {
         // Exit the application with the exception's hash code.
         System.exit(e.hashCode());
      }
   }

   // Overloaded method to take a photo using specific values for the name/width/height.
   public void TakePicture(String name, int width, int height)
   {
      _picName = name;
      _picWidth = width;
      _picHeight = height;
      TakePicture();
   }

   // Overloaded method to take a photo using a specific value for the image name.
   public void TakePicture(String name)
   {
      TakePicture(name, _picWidth, _picHeight);
   }

   // Overloaded method to take a photo using specific values for width/height.
   public void TakePicture(int width, int height)
   {
      TakePicture(_picName, width, height);
   }
}

The wrapper class should be pretty self-explanatory: calling the TakePicture() method with no parameters will use the application defaults, and calling the overloaded methods will allow you to specify the output filename and the width/height for the image. You specify the image type based on the file extension; (e.g. ".jpg", ".png", ".gif"). There are a bunch of additional raspistill options which you can easily add to the application; see the Raspberry Pi camera module page for more information.

Here's a simple application to test it out:

public class CameraTest
{
  // Define the number of photos to take.
  private static final long _numberOfImages = 5;
  // Define the interval between photos.
  private static final int _delayInterval = 5000;

  public static void main(String[] args)
  {
    try
    {
      // Create a new RaspiStill object.
      RaspiStill camera = new RaspiStill();
      // Loop through the number of images to take.
      for (long i = 0; i < _numberOfImages; ++i)
      {
        // Capture the image.
        camera.TakePicture("image" + i + ".jpg",800,600);
        // Pause after each photo.
        Thread.sleep(_delayInterval);
      }
    }
    catch (Exception e)
    {
      // Exit the application with the exception's hash code.
      System.exit(e.hashCode());
    }
  }
}

Now that I've shared this odd little sample, I'll wander back to my dungeon so I can keep playing with my Raspberry Pi.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Jun 11 2015, 20:14 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Java | Raspberry PI
Tags:
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Follow Up: Converting Text Files to Audio Files

A couple of days ago I posted a blog which I titled Creating an HTML Application to Convert Text Files to Audio Files, in which I showed how to create an HTML Application that will convert a text file to an audio file. I thought that I would follow up that article with a quick demonstration which compares some of the built-in text-to-speech voices that ship with Windows 7 and Windows 8.

For the text in this demonstration I will use Edgar Allan Poe's famous poem titled The Raven, which is one of my personal favorites. (I used to have the poem memorized as a teenager... that might have been when I was going through a Fahrenheit 451 phase.)

Click the above image
to download the text file.

Microsoft Anna (Windows 7)

For the first demo we will take a look at the poem as read by the Microsoft Anna text-to-speech voice, which ships with Windows 7. This voice is acceptable, and certainly better than the text-to-speech voices which shipped before Windows 7 was released. But still, it has a few odd quirks to it, and as a result this voice typically sounds unnatural to me.

Click the above image
to download the MP3 file.

Microsoft David (Windows 8)

For the second demo we will take a look at the poem as read by the Microsoft David text-to-speech voice, which ships with Windows 8. This voice is considerably more acceptable that Microsoft Anna, and it sounds very natural to me. It is obviously a male voice, and several people with whom I have discussed this subject seem to prefer the Microsoft David voice over all the others.

Click the above image
to download the MP3 file.

Microsoft Hazel (Windows 8)

For the third demo we will take a look at the poem as read by the Microsoft Hazel text-to-speech voice, which also ships with Windows 8. This also much better than the text-to-speech voices which shipped before Windows 7, and it has an English accent which makes it fun for converting literary works.

Click the above image
to download the MP3 file.

Microsoft Zira (Windows 8)

For the fourth and final demo we will take a look at the poem as read by the Microsoft Zira text-to-speech voice, which also ships with Windows 8. This voice is my personal favorite, as I find it the most-natural sounding of all the text-to-speech voices which ship with Windows 7; this is the voice that I used for all of my textbook reading assignments.

Click the above image
to download the MP3 file.

In Closing

As a parting thought, the text-to-speech voices which ship with Windows 8 are extremely good in my opinion; they sound very natural, and they are very easy to understand. As a result, I have a tendency to speed up the playback a little when I am using the script which I shared in my previous blog. That being said, I hope these samples help to demonstrate the various text-to-speech voices that are available in the recent versions of Windows.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: May 28 2015, 19:59 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Scripting | HTMLA
Tags:
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Creating an HTML Application to Convert Text Files to Audio Files

I'd like to take a brief departure from my normal collage of web-related and server-management examples and share a rather eclectic code sample.

Here's the scenario: I am presently working on another college degree, and I was recently taking a class which required a great deal of reading.  These assignments were all in digital form: I was using PDF or Kindle-based versions of the textbooks, and the remaining reading consisted of online articles. However, I am also an avid bicyclist, and the voluminous amount of reading was preventing me from going on some of my normal weekly rides. This gave me an idea: if I could convert the digital text to audio, I could bring my assignments with me on my longer rides and have my MP3 player read my assignments to me as I pedaled my way around the Arizona deserts.

I had experimented with Microsoft's built-in text-to-speech features some years ago, so I thought that this would be the perfect opportunity to revisit some of those APIs and write a full application to do the conversions for me. That being said, I could have written this application by using C# (which is my preferred language), but I decided to create an HTML Application for two reasons: 1) it is easier for me to share HTMLA applications in a blog, and 2) I keep this application in my OneDrive, so it's easier for me to modify the source code on systems where I don't have Visual Studio installed.

With that in mind, here is the resulting script which will convert a Text File to a Wave (*.WAV) File.

Using the HTML Application

As with most of my HTML Applications, the user interface is pretty simple to use; when you double click the HTA file, it will present you with the following user interface:

Clicking the Browse button will obviously allow you to browse to a text file, clicking the Close button will close the application, and clicking the Write File button will create a Wave file that is in the same path as the source text file. For example, if you have a text file at "C:\Text\Test.txt", the script will create a Wave file at "C:\Text\Test.txt.wav".

That being said, there are a few options which you can set:

  • Depending on your version of Windows and which languages you have installed, you will be presented with a list of available voices in the first drop-down menu; the following example is from a Windows 8 computer:
  • The second drop-down menu allows you to vary the playback speed; sometimes I prefer the playback speed to be slighter faster than normal:
  • The third drop-down menu allows you to alter the volume of the resulting Wave file:

Note: You can modify the script to alter the values that are used in the playback speed and volume drop-down menus, but the list of voices is obtained dynamically from your operating system.

Creating the HTML Application

To create this HTML Application, save the following HTMLA code as "Text to Wave File.hta" to your computer, and then double-click its icon to run the application. (Note that in a few places I added code comments which contain the MSDN URL for the APIs that I am using for this sample.)

<html>
<head>
<title>Text-to-Speech Writer</title>
<HTA:APPLICATION
  APPLICATIONNAME="Text-to-Speech Writer"
  ID="TextToSpeech"
  VERSION="1.0"
  BORDER="dialog"
  BORDERSTYLE="static"
  INNERBORDER="no"
  CAPTION="yes"
  SYSMENU="no"
  MAXIMIZEBUTTON="no"
  MINIMIZEBUTTON="no"
  SCROLL="no"
  SCROLLFLAT="yes"
  SINGLEINSTANCE="yes"
  CONTEXTMENU="no"
  SELECTION="no"/>
</head>

<script language="VBScript">

Option Explicit

Dim blnCancelBubble

' ----------------------------------------
' 
' OnLoad event handler for the application.
' 
' ----------------------------------------

Sub Window_OnLoad
  blnCancelBubble = False
  ' Set up the UI dimensions.
  Const intDialogWidth = 550
  Const intDialogHeight = 125
  ' Specify the window position and size.
  Self.resizeTo intDialogWidth,intDialogHeight
  Self.moveTo (Screen.AvailWidth - intDialogWidth) / 2,_
    (Screen.AvailHeight - intDialogHeight) / 2
  ' Load the list of text-to-speech voices into the drop-down menu.
  ' See the notes in WriteFile() for more information.
  Dim objSAPI, objVoice, objSelect, objOption
  Set objSAPI = CreateObject("SAPI.SpVoice")
  For Each objVoice In objSAPI.GetVoices("","")
    Set objSelect = Document.getElementById("optVoices")
    Set objOption = Document.createElement("option")
    objOption.text = objVoice.GetDescription() 
    objSelect.Add objOption
  Next
End Sub

' ----------------------------------------
' 
' Click handler for the Write button.
' 
' ----------------------------------------

Sub btnWrite_OnClick()
  ' Test for a file name.
  If Len(txtFile.Value) > 0 Then
    ' Test if we need to cancel bubbling of events.
    If blnCancelBubble = False Then
        ' Write the input file.
        Call WriteFile(txtFile.Value)
      End If
    End If
    ' Specify whether to bubble events.
    blnCancelBubble = IIf(blnCancelBubble=True,False,True)
End Sub

' ----------------------------------------
' 
' Change handler for the input box.
' 
' ----------------------------------------

Sub txtFile_OnChange()
  ' Enable the Write button.
  btnWrite.Disabled = False
  ' Enable event bubbling.
  blnCancelBubble = False
End Sub

' ----------------------------------------
' 
' Click handler for the Close button.
' 
' ----------------------------------------

Sub btnClose_OnClick()
  ' Test if we need to cancel bubbling of events.
  If blnCancelBubble = False Then
    ' Prompt the user to exit.
    If MsgBox("Are you sure you wish to exit?", _
      vbYesNo+vbDefaultButton2+vbQuestion+vbSystemModal, _
      TextToSpeech.applicationName)=vbYes Then
      ' Enable event bubbling.
      blnCancelBubble = True
      ' Close the application.
      Window.close
    End If
  End If
  ' Specify whether to bubble events.
     blnCancelBubble = IIf(blnCancelBubble=True,False,True)
End Sub

' ----------------------------------------
' 
' This is an ultra-lame workaround for the lack
' of a DoEvents() feature in HTA applications.
' 
' ----------------------------------------

Sub DoEvents()
  On Error Resume Next
  ' Create a shell object.
  Dim objShell : Set objShell = CreateObject("Wscript.Shell")
  ' Call out to the shell and essentially do nothing.
  objShell.Run "ver", 0, True
End Sub

' ----------------------------------------
' 
' This is an ultra-lame workaround for the lack
' of an IIf() function in vbscript applications.
' 
' ----------------------------------------

Function IIf(tx,ty,tz)
  If (tx) Then IIf = ty Else IIf = tz
End Function

' ----------------------------------------
' 
' Main text-to-speech function
' 
' ----------------------------------------

Sub WriteFile(strInputFileName)
  On Error Resume Next
  Dim objFSO
  Dim objFile
  Dim objSAPI
  Dim objFileStream
  Dim strOldTitle
  Dim strOutputFilename
  Const strProcessing = "Creating WAV file... "
  
  ' Define the audio format as 44.1kHz / 16-bit audio.
  ' See http://msdn.microsoft.com/en-us/library/ms720595.aspx
  Const SAFT44kHz16BitStereo = 35
  ' Allow text to be read as well as written.
  ' See http://msdn.microsoft.com/en-us/library/ms720858.aspx
  Const SSFMCreateForWrite = 3
  
  ' Define the output WAV filename.
  strOutputFilename = strInputFileName & ".wav"

  ' Create a file system object and open the input file.
  Set objFSO = CreateObject("Scripting.FileSystemObject")
  Set objFile = objFSO.OpenTextFile(strInputFileName, 1)
  
  ' Disable the form fields.
  optVoices.Disabled = True
  optRate.Disabled = True
  optVolume.Disabled = True
  txtFile.Disabled = True
  btnWrite.Disabled = True
  btnClose.Value = "Cancel"

  ' Test for an error.
  If Err.Number <> 0 Then
      MsgBox "Error: " & Err.Number & vbCrLf & Err.Description
  Else
    ' Store the original dialog title.
    strOldTitle = Document.title
    ' Display a status message.
    Document.title = strProcessing & Time()
    ' Pause briefly to let the screen refresh and capture events.
    Call DoEvents()
    ' Create a text-to-speech object.
    ' See http://msdn.microsoft.com/en-us/library/ms720149.aspx
    Set objSAPI = CreateObject("SAPI.SpVoice")
    ' Create a SAPI file stream object.
    ' See http://msdn.microsoft.com/en-us/library/ms722561.aspx
    Set objFileStream = CreateObject("SAPI.SpFileStream")
    ' Specify the stream format.
    ' See http://msdn.microsoft.com/en-us/library/ms720998.aspx
    objFileStream.Format.Type = SAFT44kHz16BitStereo
    ' Open the output file stream.
    objFileStream.Open strOutputFilename, SSFMCreateForWrite
    ' Specify the output file stream.
    ' See http://msdn.microsoft.com/en-us/library/ms723597.aspx
    Set objSAPI.AudioOutputStream = objFileStream
    
    ' Specify the speaking rate.
    ' See http://msdn.microsoft.com/en-us/library/ms723606.aspx
    objSAPI.Rate = optRate.Options(optRate.SelectedIndex).Value
    ' Specify the speaking volume.
    ' See http://msdn.microsoft.com/en-us/library/ms723615.aspx
    objSAPI.Volume = optVolume.Options(optVolume.SelectedIndex).Value
    ' Specify the voice to use.
    ' See http://msdn.microsoft.com/en-us/library/ms723601.aspx
    ' See http://msdn.microsoft.com/en-us/library/ms723614.aspx
    Set objSAPI.Voice = objSAPI.GetVoices("","").Item(optVoices.SelectedIndex)
    
    ' Loop through the lines in the input file.
    Do While Not objFile.AtEndOfStream
      ' Test if we need to cancel bubbling of events.
      If blnCancelBubble = True Then
        Exit Do
      Else
        ' Display a status message.
        Document.title = strProcessing & Time()
        ' Pause briefly to let the screen refresh and capture events.
        Call DoEvents()
        ' Speak one line from the input file.
        ' See http://msdn.microsoft.com/en-us/library/ms723609.aspx
        objSAPI.Speak objFile.ReadLine
      End If
    Loop
    ' Close the output file stream.
    objFileStream.Close
  End If

  ' Close the input file.
  objFile.Close

  ' Destroy all objects.
  Set objFileStream = Nothing
  Set objSAPI = Nothing
  Set objFile = Nothing
  Set objFSO = Nothing
  
  ' Reset the original dialog title.
  Document.title = strOldTitle

  ' Notify the user that the file has been written.
  MsgBox "Finished!", vbInformation, strOldTitle
  
  ' Re-enable the form fields.
  btnClose.Value = "Close"
  optVoices.Disabled = False
  optRate.Disabled = False
  optVolume.Disabled = False
  txtFile.Disabled = False
  btnWrite.Disabled = False

End Sub

</script>

<body bgcolor="white" id="HtmlBody">
<div id="FormControls">
  <table>
    <tr>
      <td align="left">
        <input type="file"
        style="width:250px;height:22px"
        name="txtFile"
        id="txtFile"
        onchange="txtFile_OnChange">
      </td>
      <td align="left">
        <input type="button"
        style="width:125px;height:22px"
        name="btnWrite"
        id="btnWrite"
        value="Write File"
        disabled
        onclick="btnWrite_OnClick">
      </td>
      <td align="right">
        <input type="button"
        style="width:125px;height:22px"
        name="btnClose"
        id="btnClose"
        value="Close"
        onclick="btnClose_OnClick">
      </td>
    </tr>
    <tr>
      <td align="left">
        <select name="optVoices"
          style="width:250px;height:22px">
        </select>
      </td>
      <td align="left">
        <select name="optRate"
          style="width:125px;height:22px">
          <option value="-2">Slowest</option>
          <option value="-1">Slower</option>
          <option value="0" selected>Normal Speed</option>
          <option value="1">Faster</option>
          <option value="2">Fastest</option>
        </select>
      </td>
      <td align="right">
        <select name="optVolume"
          style="width:125px;height:22px">
          <option value="25">25% Volume</option>
          <option value="50">50% Volume</option>
          <option value="75">75% Volume</option>
          <option value="100" selected>Full Volume</option>
        </select>
      </td>
    </tr>
  </table>
</div>
</body>
</html>

Note that I intentionally chose to have this HTML Application convert the text to audio one line at a time; this slows the down the conversion process, but it allows the conversion to be cancelled if necessary. (My original version of this script would convert an entire text file at one time; since there was no way to cancel the operation, the script appeared to hang when converting larger text files.)

Additional Notes

Once you have created a Wave (*.WAV) file, you can optionally convert it to an MP3 file for use in an MP3 player or mobile phone. (Most devices should playback Wave files, but MP3 files are considerably smaller and more portable.) There are a variety of Wave-to-MP3 converters out there, but I prefer to use the LAME encoder, which is an open-source code project that is available on SourceForge. Once you have the LAME encoder project compiled, (or you have located and downloaded a pre-compiled version), you can use LAME.EXE from a command prompt to convert your Wave files into MP3 files.

That being said, I prefer to automate as much as possible, so I have written a batch file which converts all of the Wave files in a directory to MP3 files and renames the source Wave files with a "*.old" filename extension:

@echo off

for /f "usebackq delims=|" %%a in (`dir /b *.wav`) do (
  for %%b in (^"%%a^") do (
    if not exist "%%~db%%~pb%%~nb.mp3" (
      lame.exe -b 128 -m j "%%a" "%%~nb.mp3"
    )
    if exist "%%~db%%~pb%%~nb.mp3" (
      move "%%a" "%%a.old"
    )
  )
)

Note that the above batch file was written for text-to-speech use, and as such it defines a bit rate of 128kbps, which would be pretty low for music files. If you want to repurpose this batch file for higher bitrates, modify the value of the "-b" parameter for the LAME.EXE command.

That wraps it up for today's post.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

Posted: May 27 2015, 08:18 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: HTMLA | Scripting | VBScript
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

How to create an HTML Application to configure your IIS SMTP Username and Password settings

Like many IIS administrators, I usually install the local SMTP service on my IIS servers when I am setting up a new server from scratch. When I install the SMTP service, I configure it so that it only listens on the IP address of 127.0.0.1, so it can only send emails which originate on the server itself. What's more, I configure the SMTP service to relay all emails to a downstream SMTP service which can send emails out to the Internet. By configuring these options, I can write my ASP.NET, PHP, and Classic ASP applications so that they use the local SMTP service for all email-related functionality, which acts as a sort of message server for my applications. This system works great, and I have used this particular setup since the days of IIS 4.0. (Which was released in late 1997, as you may recall.)

That being said, in the interests of security, sometime ago I started using a downstream SMTP service which requires user credentials, (that way no one could use the downstream server anonymously). As an additional security step, I use an account which requires that the credentials are changed every 30 days or so. This is always a good security practice for a myriad of obvious reasons, but this meant that I needed to update the SMTP username/password settings in my IIS configuration settings every 30 days.

With that in mind, many years ago I wrote a simple VBScript application which I would use to update those credentials. At first I would simply enter the credentials directly into the script, then I would run it to update IIS, and then I would strip the credentials from the script. Needless to say, this was pretty low-tech - even considering that this was 17 or 18 years ago. I eventually updated the script so that it would use VBScript Input Boxes to prompt me for the credentials, so I no longer needed to store the credentials in the script itself. (Sometime after that I rewrote the script so that it would read the existing values from the IIS settings and pre-populate the input boxes.)

Jumping ahead a couple of years, I decided to rewrite the script as an HTML Application, which offered me considerably more options from a user interface perspective. That script has been serving me faithfully for some time now, so I thought that it would make a good blog subject.

Using the HTML Application

Using the application is pretty straight-forward; when you double click the HTA file, it will present you with the following user interface:

The script will read any existing credentials from your IIS settings and use those to pre-populate the interface. If no existing credentials are found, it will pre-populate the interface with the username of the currently-logged-on user.

Clicking Update will update your IIS settings, clicking Reset will reset the values back to the last saved version, and clicking Close will obviously close the application, but only after it has checked if you have any unsaved changes.

Creating the HTML Application

To create this HTML Application, save the following HTMLA code as "Reset SMTP Password.hta" to your computer, and then double-click its icon to run the application.

<html>
<head>
<title>Reset SMTP Password</title>
<HTA:APPLICATION
  APPLICATIONNAME="Reset SMTP Password"
  ID="ResetSmtpPassword"
  VERSION="1.0"
  BORDER="dialog"
  BORDERSTYLE="static"
  INNERBORDER="no"
  CAPTION="yes"
  SYSMENU="no"
  MAXIMIZEBUTTON="no"
  MINIMIZEBUTTON="no"
  SCROLL="no"
  SCROLLFLAT="yes"
  SINGLEINSTANCE="yes"
  CONTEXTMENU="no"
  SELECTION="no"/>
<style>
<!--
body,input
{
font-family:calibri,arial;
font-size:9pt;
color: #000;
background-color: #fff;
}
table,td,th
{
border:none;
}
--> </style> </head> <script language="VBScript"> Option Explicit ' Define the global variables. Dim objWMIService, objIIsSmtpServer Dim strRouteUserName, strRoutePassword Dim blnCancelBubble, blnPendingUpdates ' ---------------------------------------- ' ' Initialization method for the application. ' ' ---------------------------------------- Sub Window_OnLoad ' Define the local variables. Dim objNetwork ' Set up the UI dimensions. Const intDialogWidth = 280 Const intDialogHeight = 220 ' Specify the window position and size. Self.resizeTo intDialogWidth,intDialogHeight Self.moveTo (Screen.AvailWidth - intDialogWidth) / 2, _ (Screen.AvailHeight - intDialogHeight) / 2 ' Enable events. blnCancelBubble = False blnPendingUpdates = False ' Set up some base objects for the local computer and default SMTP instance. ' Note that these settings can be customized for a different computer or SMTP instance. Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2") Set objIIsSmtpServer = GetObject("IIS://localhost/SmtpSvc/1") ' Retrieve the current username/password from the SMTP settings. strRouteUserName = objIIsSmtpServer.RouteUserName strRoutePassword = objIIsSmtpServer.RoutePassword ' Verify that a username was retrieved; otherwise, use the logged-on user. If Len(strRouteUserName)=0 Then Set objNetwork = CreateObject("WScript.Network") strRouteUserName = IIf(Len(objNetwork.UserDomain)>0, _ objNetwork.UserDomain & "\","") & objNetwork.UserName Set objNetwork = Nothing blnPendingUpdates = True End If ' Store the username/password values in the UI. txtUsername.value = strRouteUserName txtPassword.value = strRoutePassword End Sub ' ---------------------------------------- ' ' Implement the missing IIf() function. ' ' ---------------------------------------- Function IIf(tx,ty,tz) If (tx) Then IIf = ty Else IIf = tz End Function ' ---------------------------------------- ' ' Click handler for the Close button. ' ' ---------------------------------------- Sub btnClose_OnClick() ' Test if we need to cancel bubbling of events. If blnCancelBubble = False Then ' Check if there are pending updates. If blnPendingUpdates = False Then ' If not, then close the application. Window.close ' Prompt the user to exit. ElseIf MsgBox("You have not saved your changes." & vbCrLf & _ "Are you sure you wish to exit?", _ vbYesNo+vbDefaultButton2+vbQuestion+vbSystemModal, _ ResetSmtpPassword.applicationName)=vbYes Then ' Enable event bubbling. blnCancelBubble = True ' Close the application. Window.close End If End If ' Specify whether to bubble events. blnCancelBubble = IIf(blnCancelBubble=True,False,True) End Sub ' ---------------------------------------- ' ' Change handler for text boxes. ' ' ---------------------------------------- Sub Textbox_OnChange() ' Flag the application as having updates pending. blnPendingUpdates = True End Sub ' ---------------------------------------- ' ' Focus handler for text boxes. ' ' ---------------------------------------- Sub Textbox_OnFocus(objTextbox) ' Select the text in the textbox. objTextbox.Select End Sub ' ---------------------------------------- ' ' Click handler for the Reset button. ' ' ---------------------------------------- Sub btnReset_OnClick() ' Reset the username/password values in the UI. txtUsername.value = strRouteUserName txtPassword.value = strRoutePassword blnPendingUpdates = False End Sub ' ---------------------------------------- ' ' Click handler for the Update button. ' ' ---------------------------------------- Sub btnUpdate_OnClick() ' Catch bubbled events. If blnCancelBubble = True Then blnCancelBubble = False Exit Sub End If ' Verify valid data. If Len(txtUsername.value)=0 Or Len(txtPassword.value)=0 Then ' Inform the user that they made a mistake. MsgBox "An invalid username or password was entered.", _ vbCritical + vbOKOnly, ResetSmtpPassword.applicationName ' Cancel event bubbling. blnCancelBubble = True Else ' Store the username/password values for the SMTP server. objIIsSmtpServer.RouteUserName = txtUsername.value objIIsSmtpServer.RoutePassword = txtPassword.value objIIsSmtpServer.SetInfo ' Save the username/password values. strRouteUserName = txtUsername.value strRoutePassword = txtPassword.value ' Flag the application as having no updates pending. blnPendingUpdates = False ' Cancel event bubbling. blnCancelBubble = True End If End Sub </script> <body bgcolor="white" id="HtmlBody"> <div id="FormControls"> <table> <tr><td>Please enter your SMTP credentials:</td></tr> <tr> <td align="left"> <input type="text" style="width:250px;height:22px" name="txtUsername" id="txtUsername" onchange="Textbox_OnChange()" onfocus="Textbox_OnFocus(txtUsername)" /> </td> </tr> <tr> <td align="left"> <input type="password" style="width:250px;height:22px" name="txtPassword" id="txtPassword" onchange="Textbox_OnChange()" onfocus="Textbox_OnFocus(txtPassword)" /> </td> </tr> <tr> <td align="right"> <input type="button" style="width:125px;height:22px" name="btnUpdate" id="btnUpdate" value="Update" onclick="btnUpdate_OnClick()" /> </td> </tr> <tr> <td align="right"> <input type="button" style="width:125px;height:22px" name="btnReset" id="btnReset" value="Reset" onclick="btnReset_OnClick()" /> </td> </tr> <tr> <td align="right"> <input type="button" style="width:125px;height:22px" name="btnClose" id="btnClose" value="Close" onclick="btnClose_OnClick()" /> </td> </tr> </table> </div> </body> </html>

That's all that there is to it, although you might want to restart your SMTP service after you have made these changes.

Additional Notes

On a related topic, I get asked from time to time why I like to use HTML Applications (HTMLA) for some of my scripting examples, and the answer is quite simple: it is very easy to create powerful scripts in a very short amount of time which have sophisticated user interfaces and no compilation requirements.

I use Adersoft's HtaEdit as my IDE for HTMLA, which allows me to do normal development tasks like configuring project options, setting breakpoints, and stepping through my code.


Note: Click the image above to open it full-size in a separate window.

That being said, I have been experimenting with creating user interfaces in PowerShell, and it looks like it has some real promise for creating great UIs, too. But for now I only use PowerShell to create command line scripts, I use HTMLA to create cool UIs for administrative scripts, and I use C# to create "real" applications.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: May 22 2015, 09:11 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Using the Ceton Echo with Windows Media Center

I don't usually post reviews, but I thought that this subject was worth mentioning in case anyone else is having problems with Windows Media Center (WMC) and Linksys Media Center Extenders (MCEs).

Here’s the situation, I used to have a pair of Linksys MCEs that I used with my WMC computer; a DMA2100 and a DMA2200. (Although when I bought an Xbox 360 a few years ago, I configured it as an extender, and I gave the DMA2100 to my dad to use with his WMC computer.)

DMA2100 DMA2200
Linksys DMA2100 Linksys DMA2200

That being said, the Linksys MCEs never worked perfectly for me. They worked fine with DVR-MS, WTV, and WMV files, but for some reason they never worked well with MP4 files – which included my entire library of personal DVDs that I painstakingly ripped and stored in a Media Library on a NAS unit. However, I should mention that MP4 files played just fine on the WMC computer itself, just not on the Linksys MCEs.

I realize that all of the file types which I listed are just wrappers around the underlying media, so I was always perplexed as to why an MP4 file would never work with the fast-forward, skip, rewind, or pause buttons on my Linksys MCEs even though a WMV file would. What’s more, most MP4 files would play for about a minute, then the playback would freeze/skip/etc. for a little bit, and then it would usually stabilize for the rest of the video. (Although sometimes the video would never come back and I had to stop the video or reset the MCE.)

I should point out that all of my devices are hard-wired to gigabit networking hardware, so throughput should never have been a problem. (In fact, running the networking tests from the Linksys MCEs always showed bandwidth off the scale.) I eventually decided that the DMA2200 would have to be limited to just playing back recorded TV, which made it a rather bad return on investment.

For the past few years I had thought that the problem was simply some sort of compatibility issue with Windows Media Center and MP4 files when using an MCE, but I never had any problems with my Xbox 360 when using it as an MCE. In fact, the Xbox works so well that I have disconnected my WMC computer from my TV and now I exclusively use the Xbox to control my WMC.

Xbox-360
Xbox 360

Seeing as how the Xbox was working so well, it finally dawned on me that the problem had to be something internal to the Linksys MCEs. With that in mind, I started shopping around for a second Xbox 360 to replace my remaining Linksys MCE, but first I decided to read some of the Ceton Echo reviews on Amazon.

Several of the reviewers specifically mentioned my exact scenario; they were using Linksys MCEs and they were having problems with the video freezing/skipping, and the Echo resolved the problem. With that in mind, I decided to give the Echo a try.

Ceton-Echo
Ceton Echo

My Ceton Echo arrived a few days ago, and it was a breeze to set up. The footprint is much smaller than either the Linksys DMA2100 or DMA2200, and it's obviously a lot smaller than an Xbox 360. Another nice feature was that the Echo accepted commands from my existing Linksys remote; this was great for me because I had already configured my Linksys remote to control my TV, so I didn't have to change anything else in my setup if I didn't want to.

All of that being said, the most-important benefit after replacing my Linksys MCEs with the Ceton Echo is – no more skipping or freezing when playing MP4 files, and I can use the skip-forward, skip-backward, and pause buttons on the remote when I am viewing MP4 files. One small downside is that the fast-forward and reverse buttons do not seem to work with MP4 files, but I can live with that.

So in closing, if you're using a Linksys Media Center Extender and you're having issues with it, you might want to seriously consider replacing it with a Ceton Echo.

Posted: Apr 09 2015, 12:59 by bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Command-Line Utility to Create BlogEngine.NET Password Hashes

I ran into an interesting predicament the other day, and I thought that both the situation and my solution were worth sharing. Here's the scenario: I host websites for several family members and friends, and one of my family member's uses BlogEngine.NET for her blog. (As you may have seen in my previous blogs, I'm a big fan of BlogEngine.NET.) In any event, she forgot her password, so I logged into the admin section of her website, only to discover that there was no way for me to reset her password – I could only reset my password. Since it's my webserver, I have access to the physical files, so I decided to write a simple utility that can create the requisite SHA256/BASE64 password hashes that BlogEngine.NET uses, and then I can manually update the Users.xml file with new password hashes as I create them.

With that in mind, here is the code for the command-line utility:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace BlogEnginePasswordHash
{
  class Program
  {
    static void Main(string[] args)
    {
      // Verify that a single argument was passed to the application...
      if (args.Length != 1)
      {
        // ...if not, reply with generic help message.
        Console.WriteLine("\nUSAGE: BlogEnginePasswordHash <password>\n");
      }
      // ...otherwise...
      else
      {
        // Retrieve a sequence of bytes for the password argument.
        var passwordBytes = Encoding.UTF8.GetBytes(args[0]);
        // Retrieve a SHA256 object.
        using (HashAlgorithm sha256 = new SHA256Managed())
        {
          // Hash the password.
          sha256.TransformFinalBlock(passwordBytes, 0, passwordBytes.Length);
          // Convert the hashed password to a Base64 string.
          string passwordHash = Convert.ToBase64String(sha256.Hash);
          // Display the password and it's hash.
          Console.WriteLine("\nPassword: {0}\nHash: {1}\n", args[0], passwordHash);
        }
      }
    }
  }
}

That code snippet should be pretty self-explanatory; the application takes a single argument, which is the password to hash. Once you enter a password and hit enter, the password and it's respective hash will be displayed.

Here are a few examples:

C:\>BlogEnginePasswordHash.exe "This is my password"

Password: This is my password
Hash: 6tV+IGzvN4gaQ0vmCWNHSQ0UQ0WgW4+ThJuhpXR6Z3c=

C:\>BlogEnginePasswordHash.exe Password1

Password: Password1
Hash: GVE/3J2k+3KkoF62aRdUjTyQ/5TVQZ4fI2PuqJ3+4d0=

C:\>BlogEnginePasswordHash.exe Password2

Password: Password2
Hash: G+AiJ1Cq84iauVtdWTuhLk/xBGR0cC1rR3n0tScwWyM=

C:\>

Once you have created password hashes, you can paste those into the Users.xml file for your website:

<Users>
  <User>
    <UserName>Alice</UserName>
    <Password>GVE/3J2k+3KkoF62aRdUjTyQ/5TVQZ4fI2PuqJ3+4d0=</Password>
    <Email>alice@fabrikam.com</Email>
    <LastLoginTime>2015-01-31 01:52:00</LastLoginTime>
  </User>
  <User>
    <UserName>Bob</UserName>
    <Password>G+AiJ1Cq84iauVtdWTuhLk/xBGR0cC1rR3n0tScwWyM=</Password>
    <Email>bob@fabrikam.com</Email>
    <LastLoginTime>2015-01-31 01:53:00</LastLoginTime>
  </User>
</Users>

That's all there is to do. Pretty simple stuff.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

Posted: Jan 30 2015, 19:36 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: BlogEngine.NET
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Nineteen Years at Microsoft

Microsoft has an interesting unofficial tradition for employees on their anniversary with the company: we are supposed to bring in one pound of M&M's for each year that we have been with Microsoft.

That being said, when I reached my 10-year anniversary with the company, I decided that 10 pounds or more of M&M's were way too many, and a recent trip to the Cayman Islands provided me with a much better alternative: Caribbean Rum Cakes.

Tortuga_Rum_Cakes

For each subsequent anniversary, I would bring in a rum cake for each year since my date of hire, and these proved to be a big hit with my coworkers. So much so that when I changed teams, I had coworkers ask me if they could still drop by and have rum cake on my anniversary.

However, this past year I began working remotely, so I did not order any rum cakes that year. That was kind of sad for me, because I usually looked forward to my annual tradition. With that in mind, I hadn't really paid attention as my anniversary was approaching this year.

Needless to say, I was quite surprised this morning when I entered my office and discovered that my loving spouse had ordered several rum cakes just for me to celebrate the day…

I have a pretty cool spouse. ;-]

Posted: Dec 18 2014, 11:07 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Microsoft
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

FTP Clients - Part 16: NetDrive

For the next installments in my series about FTP clients, I will be taking a look at two FTP redirectors at the same time. In this specific blog post, I will focus on NetDrive (from Bdrive Inc.), whereas my previous post looked at WebDrive (from South River Technologies).

At the time of this blog's writing, NetDrive is a for-retail FTP client and redirector which is available from the following URL:

http://www.netdrive.net/

For this blog post I will be using NetDrive version 2.3.2.

NetDrive 2.3 Overview

NetDrive is different from many of the other FTP clients that I have reviewed because it is an Internet protocol redirector, meaning that it allows you to map drive letters to a variety of Internet repositories. When you install and open NetDrive, you are presented with the list of supported Internet protocols and repositories which you can use for mapping drives:

As you can see from the illustration above, NetDrive's list of support technologies is quite extensive: DropBox, Box.net, Google Drive, OneDrive, Amazon S3, Openstack Swift, FTP, SFTP, and WebDAV.

When you add a drive or configure the settings for one of the default drives, you are presented with a dialog box where you can enter the settings for the drive connection; note that there are very few settings for FTP connections:

As you add drives, the NetDrive user interface will display the drives and their current connection status:

As an added touch, NetDrive customizes its drive icons in Windows Explorer, so you can easily see the type of mapped drive for each connection:

I would love to take an in-depth look at all of the supported protocols in this review, but this series is about FTP clients, so I'll move on to the FTP-specific features that I normally review.

Using NetDrive 2.3 with FTP over SSL (FTPS)

NetDrive 2.3 has built-in support for FTP over SSL (FTPS), although it only appears to support Explicit FTPS - and it does so in a confusing way. When you are editing the settings for an FTP drive connection, you need to check the box for SSL/TLS in order to enable FTPS. Unfortunately, when you do so, the dialog box will change the port to 990, which is the port number for Implicit FTPS; however, in my testing I could not get Implicit FTPS to work:

With the above information in mind, I needed to manually change the port number back to 21 in order to use Explicit FTPS with NetDrive:

Using NetDrive 2.3 with True FTP Hosts

True FTP hosts are not supported natively by NetDrive 2.3, and there are no settings which allow you to customize the login environment in order to work around this situation.

Using NetDrive 2.3 with Virtual FTP Hosts

NetDrive 2.3's login settings allow you to specify the virtual host name as part of the user credentials by using syntax like "ftp.example.com|username" or "ftp.example.com\username", so you can use virtual FTP hosts with NetDrive 2.3.

Scorecard for NetDrive 2.3

This concludes my quick look at a few of the FTP features that are available with NetDrive 2.3, and here are the scorecard results:

Client
Name
Directory
Browsing
Explicit
FTPS
Implicit
FTPS
Virtual
Hosts
True
HOSTs
Site
Manager
Extensibility
NetDrive 2.3.2 N/A Y N1 Y N2 Y N/A
Notes:
  1. Despite several attempts, I could not get NetDrive to work with Implicit FTPS.
  2. I could find no way to customize an FTP connection in order to enable true FTP hostnames.

That wraps things up for today's review of NetDrive 2.3. Your key take-aways should be: NetDrive has some nice features, and it supports a wide variety of protocols with a similar user experience; that being said, NetDrive has very few settings for drive connections, so its capabilities are somewhat limited.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Nov 29 2014, 19:42 by Bob | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: FTP | SSL
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us