All posts by Scott Gutauckis

RealVNC

I have used RealVNC in our organization for many years and the other day I was kind of bored and decided to make an app that would tell me which PC’s had it installed and was it listening for the viewer.

My situation is this: User calls or sends in support request, I open up a log file looking for the users IP or machine name, enter it in the vnc viewer and try to connect. I wanted a faster, easier way to do this with less steps.

For this I would read in the log files for the user logons, then test each PC the user was last logged onto to see if it had vnc listening on port 5900. Easy enough.

For those interested first you will need to setup a logon script, here is mine:

I have this script along with the logoff script in the DC NETLOGON share.

In Group Policy I use the User Configuration\Policies\Windows Settings\Scripts\Logon and logoff settings to specify my script location. Now each time the user logs off and on it makes an entry in their corresponding log.

Here is the logoff script, its the same except “logon” is replaced with “logoff”:

So that gives me the file I need for the next step which is loading the list with the following:

This function calls GetUsers to load the list of users that we currently have log files for and then if we are checking for a vnc connection call TestSelectedPort and set the image for the list view item appropriately.

Here is the project zipped for you with all the code.

Remote Project

Once the application is run you are first presented with a static list of the users PC’s. Depending on what view you choose it will either be a picture of a pc with machine name of if you choose details then you will see a list showing the user, pc name, last logon and the ip.

I blocked out the username for security reasons, but you get the idea.

If we click the Check VNC button this is what you end with showing which PC’s are up and running VNC.

Go ahead and download the file and give it a try.

Accessing and modifying user settings between applications in VB.Net

Recently and for the first time I coded a Windows service. A basic service that runs in the background and based upon a set interval would poll all the PC’s and servers on my network looking for archived event logs. I set my logs to save when they reach 16mb. On some boxes due to the amount of auditing I do this was consuming hard drive space pretty rapidly and I needed a simple way to retrieve those logs, zip them up and move them to a folder that gets backed up to the cloud weekly.

Of course as I progressed my service needed some “Settings”. For example the time interval I mentioned, the folder to move the zipped files to, etc. I thought no big deal, I have some canned code with a property grid already setup I will just copy and paste then I can run my service from the command line with a switch to show the dialog. Yea figured out that won’t work. Since its my first service I am learning about the do’s, dont’s and the “that’s not happening” issues. The latter being my problem. Seems like you can’t just run a service from the command line like I thought.

Okay no big deal I will just code a small project just for editing those settings in my service. There it was again, my naivety . I did some research on the web, tried a few suggestions here and there with no luck. Then I landed on a page that someone obviously smarter than I told the group you cannot access other project user settings with vb.net. He went on to explain that the project stores the user settings in some folder with a unique hash and that it was impossible to use.

Huh, sounds like a challenge.

Well first what he said did not make sense. Of the nearly 100 projects I have coded through the years, the projects settings file is in the same folder as the EXE itself. To confirm that my service was using the settings file within its running folder I made some changes in the settings file with notepad and restarted my service. Yup using event viewer I could see my service was in fact using that file. But why? If indeed the the settings were constructed in the project settings as having a scope of “user” then that file would have to be unique for anyone that logs onto that machine, right? Well I went on to investigate some more and his explanation seems to hold water but only for Windows Forms applications and not a service application. User settings are stored in something like this: “C:\Users\gutauckis.CITYOFHOLLYHILL\AppData\Local\Hewlett-Packard Company\Settings\1.0.0.0” and a service is not “user” based. So those settings would not be stored with a user but in the applications directory.

Here is a link to a good explanation the settings architecture 

In his explanation he mentioned just storing your settings in an XML file that both of the projects would know where they are stored. Then you can use one project, the GUI to modify the settings and the service would read them at startup or during code execution. That sounds like a good idea. Since my settings file is stored with the service application and my setting gui will be in the same folder I can just read the settings file, make changes and save it back out.

Here is the code for reading and writing the settings xml file:

To use it:

That’s pretty much it. Nothing fancy. Looking at the xml reader I could probably clean it up, but its working so I will leave it alone for now.

Remember this is for a service application, not a windows forms application. You could do the same thing only you will need to know where the users application setting is stored and share that with your setting GUI application.

Run your forms app and get the Application.LocalUserAppDataPath Property. Then add that to your settings GUI app so it knows where to look. Easy enough.

Those pesky passwords

“The security expert who wrote widely accepted advice in 2003 about online passwords—use special characters and change the passwords regularly—now acknowledges that he misfired. “Much of what I did I now regret,” Bill Burr, who is 72 and retired from the National Institute of Standards and Technology, tells the Wall Street Journal.”

To those not in the know, this isn’t really anything new to us in the technology world. What makes this news is that the man who created our current “standard” is now admitting he got it wrong.

For years many security experts suggested using pass “phrases” instead of some weird conglomeration of upper case, lower case, number and special characters.

Take this for example, let’s say your password was soemthing like DMichael96, according to https://howsecureismypassword.net/ it would only take 8 months to crack that one wide open. But if we used “themanonthemooniswalkingoncheese” it would take 2 octillion years. That’s right, a 2 with 27 zeros after it or 10 to the 27th power.
Besides remembering a phrase like that is much easier to remember than some convoluted password like “5J4kRZqPYT5x”

Now we just have to wait for developers to change the way we log in. As it stands many websites limit the number of characters you may enter for a password. I even have one system where the OS allows you to set the password length to what you want but the field where you enter your password only allows for 8 characters. Weird.

In the meantime I recommend you don’t use your kids initials and their birth date or your dogs name, instead use strong passwords and a password manager to help you manage all of these passwords. I personally use LastPass. I have hundreds of passwords, there is no way I could remember them all.

Using a password manager helps you with using a different password with each site you log on to. This makes your online life much more secure. Most people don’t think much about it but if you use the same password and a hacker hacks that book of the month club you belong to, they now can, quite quickly attempt to log into every bank and credit card company in the world and get into your account.

Visit https://howsecureismypassword.net/ to test your password. You will be surprised by the results.

Sharing printers with Group Policy




I recently had the need to move all of my managed printers from one server to another. I did the normal export printers from the old server and import on the new. Nothing to note except I had one printer that wouldn’t come over. Not sure why, but I figured no big deal I would just recreate it on the new box.

After some testing I changed everyone over to the new printer GP’s. No complaints except from the Org Unit whose printer would not come over. Even though I reinstalled, created and applied the GP it would not work for nothing. I ran the usual tools, RSOP, GPRESULT nothing showed any errors. Ran the modeling wizard in GP management on the Org and got a cryptic error on the Computer and User Component Status about the deployed printers connection. Basically just said there were many errors and to look at the Application Event Log on the domains I ran the model on. Checked both DC’s and there were NO errors. Thanks MS what a big help.

It wasn’t until I ran across an article while troubleshooting about GP Preferences. I stated playing with this and got to the Printer preferences. I went to add the offending printer but discovered it wasn’t listed in Active Directory. Ah Ha!

When back to my Print Server right clicked the printer to “List in Directory” but the option wasn’t there. Say whaaaat? Huh?

I uninstalled and re-installed the printer, but I never got the option to “List in Directory”. Did not make sense as there were many other printers, same model, make, etc and they had the option.

Then it dawned on me… What a dumb ass.

Uninstalled printer, re-installed but this time did NOT uncheck “Share”. The rest… well is history.

RPG Primer for the AS400

hello-world-1333103_640

I have been programming for nearly 30 years now. I started with BASIC on a Texas Instruments TI-99 with voice Synthesizer. https://en.wikipedia.org/wiki/Texas_Instruments_TI-99/4A
I followed that up with my very own Commodore 64 where I continued to dabble in the fine art of coding. Mostly though I spent a lot of time on line on a 300 baud modem. Oh those were the days.
Over the years I have dabbled in many different languages, but Visual Basic.NET and C# have always been my go to languages of choice when programming for the PC.
My issue is that those languages do not work with IBM on their OS400 platform. No there if you want to make this box do anything you have to learn a new language, RPG. On a few occasions in the past I looked into it and because a lack of a good simple Hello World tutorial I gave up. That is until now.
This time I had an actual need to make this box backup some files, then transfer them to the PC with FTP for backing up to the cloud. Sounds simple enough. That is if you know how to program in RPG and how to use the editor and compiler on the OS400. I did not.
Having used the AS400 and its OS for about 18 years I was familiar with getting around the box, so that helps. If this is your first time even looking at a green screen then good luck. This tutorial will get you through to a finished product but it will be a bit daunting for you. Oh lest I forget, if you screw something up while following these directions it’s on you. If you don’t know what you are doing then get someone who does. I take no responsibility for what happens and I don’t guarantee this will work for your situation.

Hello World!
First you must be using IBM i Release 7.3, Release 7.2 TR3, or Release 7.1 TR11. RDi Release 9.5.
This is because I am using the fully free form of coding. It’s much better than what was required before and is easier for programmers of other languages to use.
I began my search on Google, looking for help on programming the AS400. I came across a few books, but when I looked they really looked like they were for someone who already had programming experience on the AS400. I decided to save my money for now and continued my search. Then I came across the aforementioned IBM link. Viola! Hello World! I read through the first page a few times and realized this might work, but the directions where confusing and really incomplete.
For clarification lets go over the steps to create your first program for the AS400.
As an example we want to create a BACKUPPGM program in a library called BACKUP.
Logon to a session on your IBM AS400

Create a library using the CRTLIB command. If you already have a library you want to use then skip this step and substitute your library name in place of the one I used (BACKUP)
1

Next create a Physical File using the CRTSRCPF. Here I name my file BACKUPPF and put it in the BACKUP library I just created.

2

Now we are going to create a member using PDM. Enter WRKMBRPDM on the command line and press F4.

3

Enter the info as you see it above and press enter. You will see the resulting screen.

4

Press F6 to Create a member

5

Enter HELLO for the Source Member, RPGLE for the Source type and give it a description. Press enter when you are done. It will take you to an editor where now you can add your code.
Now let’s write a simple Hello World program
On the first line and it must be the first line of your code enter:
**FREE
This indicates we are using the free form version of code.
Next put the following:
dsply ‘Hello World’;
return;

This is what is going to flash the Hello World on the screen.
When finished you should see something like this:

6

Now press F3 to get out of the editor and accept the defaults for saving the member.
Once back to the member list, you will see your HELLO member. Put a 14 next to it to compile it. Once compiled, from a command line enter: CALL BACKUP/HELLO
Congratulations, you created your first program on the AS400.
Now take it a step further and create that program for backing up some libraries.
We already have our library ( BACKUP) and our Physical File (BACKUPPF) so all we need to do now is create a new member for the backup program.
If you are not already there get back to your member list. (Enter WRKMBRPDM on the command line and press F4.)
Press F6 to create a new member and this time enter BACKUPPGM for the Source Member and RPGLE for the Source Type. Give it a description.
Once again you are back to your editor.
Here we are concerned with three commands:
CRTSAVF – This creates a Save File. This would be the file you want to save your library to.
CLRSAVF – This clears the save file if it contains any data already.
SAVLIB – Saves the specified library to the save file we created earlier.

Now technically you can simply issue the CRTSAVF from the command line and be done with it. This is because after the first time you run your program the save files will have already been created. However for clarity and simplicity I will leave them in the program.
For our example here we want to back up two libraries named HTEDTA and HTEPGM. To do this you would enter the following lines of code in your editor:
**FREE
CRTSAVF FILE(BACKUP/HTEDTA)
CRTSAVF FILE(BACKUP/HTEPGM)
CLRSAVF FILE(BACKUP/HTEDTA)
CLRSAVF FILE(BACKUP/HTEPGM)
SAVLIB LIB(HTEDTA) DEV(*SAVF) SAVF(BACKUP/HTEDTA)
SAVLIB LIB(HTEPGM) DEV(*SAVF) SAVF(BACKUP/HTEPGM)
Now let me break it down for you.
CRTSAVF FILE(BACKUP/HTEDTA)
This creates a save file in the library BACKUP we created earlier called HTEDTA.
CLRSAVF FILE(BACKUP/HTEDTA)
This clears the save file in the library BACKUP called HTEDTA.
SAVLIB LIB(HTEDTA) DEV(*SAVF) SAVF(BACKUP/ HTEDTA)
This saves the library called HTEDTA using the device Save File to the save file called HTEDTA in the library BACKUP.
Now that you have entered all your code press F3 and save your source member.
Put a 14 next to it and compile.
To run it you can enter on the command line CALL BACKUP/ BACKUPPGM
This will now save the libraries you specified to the save files you created. From here we can now go to a PC and FTP the files down.
Open your favorite desktop file editor and enter the following:
OPEN

bin
prompt
cd BACKUP
mget *
quit
Note: It is a good idea to use a AS400 user that only has read access to the box as you are storing these options in clear text on a PC.
Save the file as ftp_options.txt
Now create a Batch file and add this to it:
ftp –s:ftp_options.txt
Save the batch file in the same folder as the ftp_options.txt file.
Now you can use this batch file manually by running for a command line or as part of the command in Task Scheduler.
My intent here was to combine learning to program the AS400 and backing up to the PC while adding some clarity for the new AS400 programmer.
I want to thank Drew Dunkel on Spiceworks for his work on backing up the AS400.
https://community.spiceworks.com/how_to/26183-how-to-automate-as400-iseries-backup-onto-windows-network
Also thanking IBM for the Hello World instructions.
https://www.ibm.com/developerworks/community/wikis/home?lang=en#!/wiki/We13116a562db_467e_bcd4_882013aec57a/page/Coding%20in%20Free-Form%20RPG%20IV%20-%20Chapter%201%20Hello%20World

Run time error 5 Invalid Procedure Call or Argument

I recently ran into an issue when using the Shell function to call an external application from a VBA Script. This was a new issue for me as my application has been in use for many years throughout the world. I was ready to release an update and when testing the “Run time error 5 Invalid Procedure Call or Argument” message appeared.

Odd, or so I thought.

My first inclination was hit Google up and find out what was going on. Looks like I wasn’t the only one being affected by this.
What I found out was the issue was caused, in my case by the fact the path I was passing as a parameter to the Shell function had spaces in it. Duh, why wouldn’t it.

Nearly all of the solutions offered by many people were to simply enclose the path with double quotes.
While some reported success with the aforementioned fix, alas I did not find joy.

As I always like to do when confronted with something that seems hopeless ( I tried all kinds of variations of Shell and ShellExecute) I decided to sleep on it.
The next morning I once again hit Google up, still no joy.

Then it happened. Captain obvious flew into my office smacked me on the back of the head and viola, I had the answer.

You see, I grew up on computers when there was no GUI and filenames were restricted to a 8.3 naming convention and paths were limited to 256 characters and NO SPACES.

So there it was. I just needed to get the short path of the long path stored in the file system.

Here is a nugget of code for you that should alleviate your troubles. Of all the solutions I found I never did find this one. I post it here for posterity in the hopes it helps someone else.

Function ShowShortPath(filespec)
Dim fs, f, s
Set fs = CreateObject(“Scripting.FileSystemObject”)
Set f = fs.GetFile(filespec)
Return f.ShortPath
End Function

This worked for me where no other solution did.

Your welcome. 🙂

Scintilla Text Editor and the Context Menu

I use Scintilla in my AS400 Query Creator application and recently needed to modify the context menu when you right click the control. As usual I searched on Google and found Stephen Swenson’s blog and his version of a class extending the Scintilla one. His class gives you the ability to modify the context menu and it was just what I needed, except I needed it in VB.Net. Credit goes to Stephen, I just translated it for VB.Net

So here it is for any one else looking:

Inherits ScintillaNET.Scintilla
Private miUndo As MenuItem
Private miRedo As MenuItem
Private miCut As MenuItem
Private miCopy As MenuItem
Private miDelete As MenuItem
Private miSelectAll As MenuItem

Public Sub New()
MyBase.New()
initContextMenu()
End Sub
Private Sub mUndo()
Me.UndoRedo.Redo()
End Sub
Private Sub mRedo()
Me.UndoRedo.Redo()
End Sub
Private Sub mCut()
Me.Clipboard.Cut()
End Sub
Private Sub mCopy()
Me.Clipboard.Copy()
End Sub
Private Sub mPaste()
Me.Clipboard.Paste()
End Sub
Private Sub mReplace()
Me.NativeInterface.ReplaceSel("")
End Sub
Private Sub mSelectAll()
Me.Selection.SelectAll()
End Sub

Private Sub initContextMenu()
Dim cm = InlineAssignHelper(Me.ContextMenu, New ContextMenu())

Me.miUndo = New MenuItem("Undo", New EventHandler(AddressOf Me.mUndo))
cm.MenuItems.Add(Me.miUndo)

Me.miRedo = New MenuItem("Redo", New EventHandler(AddressOf Me.mRedo))
cm.MenuItems.Add(Me.miRedo)

cm.MenuItems.Add(New MenuItem("-"))

Me.miCut = New MenuItem("Cut", New EventHandler(AddressOf Me.mCut))
cm.MenuItems.Add(miCut)

Me.miCopy = New MenuItem("Copy", New EventHandler(AddressOf Me.mCopy))
cm.MenuItems.Add(miCopy)

cm.MenuItems.Add(New MenuItem("Paste", New EventHandler(AddressOf Me.mPaste)))

Me.miDelete = New MenuItem("Delete", New EventHandler(AddressOf Me.mReplace))
cm.MenuItems.Add(miDelete)

cm.MenuItems.Add(New MenuItem("-"))

Me.miSelectAll = New MenuItem("Select All", New EventHandler(AddressOf Me.mSelectAll))
cm.MenuItems.Add(miSelectAll)

End Sub

Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
If e.Button = MouseButtons.Right Then
miUndo.Enabled = Me.UndoRedo.CanUndo
miRedo.Enabled = Me.UndoRedo.CanRedo
miCut.Enabled = Me.Clipboard.CanCut
miCopy.Enabled = Me.Clipboard.CanCopy
miDelete.Enabled = Me.Selection.Length > 0
miSelectAll.Enabled = Me.TextLength > 0 AndAlso Me.TextLength <> Me.Selection.Length
Else
MyBase.OnMouseDown(e)
End If
End Sub
Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
End Class

MySQL – Insert a record only when it doesn’t exist

Inserting a record into a table when it doesn’t exist is pretty simple when your table has a unique key, but what if it doesn’t?

I had that problem recently. I need to run what amounts to a hourly transfer of records from one database, AS400 DB2 to a mySQL DB.

I could not reliably with the columns in the DB SELECT only the records that have not already been SELECTED. So the next logical step was to make sure the record did not exist in the destination DB.

For this the following SQL Statement works best if you do not have a unique key:

These are my column names: UTCSID, UTLCID, UTBIYY, UTBIMM, UTBIDD, UTTACN

First two are VARCHAR, with the rest being TINYINT.

INSERT INTO UT300AP (UTCSID, UTLCID, UTBIYY, UTBIMM, UTBIDD, UTTACN) Select * FROM (SELECT ‘12321’ , ‘32345’, 16, 4, 7, 2.0) AS tmp WHERE Not EXISTS(SELECT * FROM UT300AP WHERE UTCSID = ‘12321’ AND UTLCID = ‘32345’ AND UTBIYY = 16 AND UTBIMM = 4 AND UTBIDD =7 AND UTTACN = 2.0 ) LIMIT 1;

That is what I am using. You can SELECT based upon one, two or all of your columns. In my case I need to ensure that no record existed that was exactly the same as what I was looking to INSERT.

For those of you using vb.net to work with mySQL the following code does this for me:

Dim szInsertStatement = "INSERT INTO UT300AP (UTCSID, UTLCID, UTBIYY, UTBIMM, UTBIDD, UTTACN) Select * FROM (SELECT '{0}', '{1}', {2}, {3}, {4}, {5}) AS tmp WHERE Not EXISTS(SELECT * FROM UT300AP WHERE UTCSID = '{0}' AND UTLCID = '{1}' AND UTBIYY = {2} AND UTBIMM = {3} AND UTBIDD = {4} AND UTTACN = {5} ) LIMIT 1;"

szInsertValue = String.Format(szInsertStatement, szRow.Item("UTCSID").ToString.Trim, szRow.Item("UTLCID").ToString.Trim, szRow.Item("UTBIYY").ToString.Trim, szRow.Item("UTBIMM").ToString.Trim, szRow.Item("UTBIDD").ToString.Trim, szRow.Item("UTTACN").ToString.Trim)

Resetting User Passwords in Active Directory

So after 20 years of resetting users Windows passwords I decided I would code a small application that would be easier to use to handle this task. Yes I know there are a myriad of programs out there that will let users reset their own passwords. The problem is they cost money, usually on a per user basis. Why buy the cow when the milk is free? Thus ResetPWD was born. The application shows you a few different techniques that you may find interesting. The first is cryptography. I use this to safely store passwords on the hard drive. The other technique is for saving window positions when dealing with multiple screens. Lastly deals with Active Directory, resetting user passwords and making it so they have to change their password at the next log in. Most of the code is cobbled together from bits and pieces I found online. As always use it at your own risk, there are no warranties for fit or particular purpose and if you screw something up, it’s your fault, not mine.
When you first run the program, it will present you with a settings dialog box.
Capture-Settings

LDAP: This is the LDAP connection string you need to connect to AD and reset their password
Example: LDAP://EarthWindFire:389/DC=SomeDomain, DC=com
Domain: The fully qualified domain name of… your domain.
Username: The account you will use that has the privileges to change user passwords
Password: The password for the aforementioned account.
At the suggestion of someone online I actually just created an account for this purpose.
Once you have all that set, click the okay button and you are ready to change passwords.
The main window has two areas, the first is the text area where you will type in a username for which you want to change the password.
The second textbox is the password it will be set to. Now here I always reset a password to the same password. The users know it and its simple for them to remember. You can set it to whatever you want.

Capture-Main
Using this app is easy. Once its setup all you have to do is start the app up, enter the username you want to change passwords for and hit the enter key. If it’s successful, it will tell you and in 3 seconds close automatically.
I also made it so if you press the escape key the app closes. I did this so if accidentally open the app I can easily close it without having to mouse over to it.
Source code is zipped up here. You will need Visual Studio 2015 and .NET 4.6

Task Scheduler Result Codes

Recently I wanted to create an app that involved the Windows Task Scheduler. During the process I wanted to handle all of the possible Result Codes and finding the values of them was a bear. I figure I would add them here so at least there is one more reference out there.


Module TaskResultCodes
#Region "Enumerations"
Public Enum TaskResultCodes
SCHED_S_TASK_READY = &H00041300
SCHED_S_TASK_RUNNING = &H00041301
SCHED_S_TASK_DISABLED = &H00041302
SCHED_S_TASK_HAS_NOT_RUN = &H00041303
SCHED_S_TASK_NO_MORE_RUNS = &H00041304
SCHED_S_TASK_NOT_SCHEDULED = &H00041305
SCHED_S_TASK_TERMINATED = &H00041306
SCHED_S_TASK_NO_VALID_TRIGGERS = &H00041307
SCHED_S_EVENT_TRIGGER = &H00041308
SCHED_E_TRIGGER_NOT_FOUND = &H80041309
SCHED_E_TASK_NOT_READY = &H8004130A
SCHED_E_TASK_NOT_RUNNING = &H8004130B
SCHED_E_SERVICE_NOT_INSTALLED = &H8004130C
SCHED_E_CANNOT_OPEN_TASK = &H8004130D
SCHED_E_INVALID_TASK = &H8004130E
SCHED_E_ACCOUNT_INFORMATION_NOT_SET = &H8004130F
SCHED_E_ACCOUNT_NAME_NOT_FOUND = &H80041310
SCHED_E_ACCOUNT_DBASE_CORRUPT = &H80041311
SCHED_E_NO_SECURITY_SERVICES = &H80041312
SCHED_E_UNKNOWN_OBJECT_VERSION = &H80041313
SCHED_E_UNSUPPORTED_ACCOUNT_OPTION = &H80041314
SCHED_E_SERVICE_NOT_RUNNING = &H80041315
SCHED_E_UNEXPECTEDNODE = &H80041316
SCHED_E_NAMESPACE = &H80041317
SCHED_E_INVALIDVALUE = &H80041318
SCHED_E_MISSINGNODE = &H80041319
SCHED_E_MALFORMEDXML = &H8004131A
SCHED_S_SOME_TRIGGERS_FAILED = &H0004131B
SCHED_S_BATCH_LOGON_PROBLEM = &H0004131C
SCHED_E_TOO_MANY_NODES = &H8004131D
SCHED_E_PAST_END_BOUNDARY = &H8004131E
SCHED_E_ALREADY_RUNNING = &H8004131F
SCHED_E_USER_NOT_LOGGED_ON = &H80041320
SCHED_E_INVALID_TASK_HASH = &H80041321
SCHED_E_SERVICE_NOT_AVAILABLE = &H80041322
SCHED_E_SERVICE_TOO_BUSY = &H80041323
SCHED_E_TASK_ATTEMPTED = &H80041324
SCHED_S_TASK_QUEUED = &H00041325
SCHED_E_TASK_DISABLED = &H80041326
SCHED_E_TASK_NOT_V1_COMPAT = &H80041327
SCHED_E_START_ON_DEMAND = &H80041328

End Enum

#End Region
'''

''' Pass the task result code to get the message associated with the result
'''

''' Result Code to get text for ''' Message of Result Code
Function ReturnResultCodeString(iLong As Integer) As String

Select Case iLong
Case TaskResultCodes.SCHED_S_TASK_READY
ReturnResultCodeString = "The task is ready to run at its next scheduled time."

Case TaskResultCodes.SCHED_S_TASK_RUNNING ' &H00041301
ReturnResultCodeString = "The task is currently running."

Case TaskResultCodes.SCHED_S_TASK_DISABLED ' &H00041302
ReturnResultCodeString = "The task will not run at the scheduled times because it has been disabled."

Case TaskResultCodes.SCHED_S_TASK_HAS_NOT_RUN ' &H00041303
ReturnResultCodeString = "The task has not yet run."

Case TaskResultCodes.SCHED_S_TASK_NO_MORE_RUNS ' &H00041304
ReturnResultCodeString = "There are no more runs scheduled for this task."

Case TaskResultCodes.SCHED_S_TASK_NOT_SCHEDULED ' &H00041305
ReturnResultCodeString = "One or more of the properties that are needed to run this task on a schedule have not been set."

Case TaskResultCodes.SCHED_S_TASK_TERMINATED ' &H00041306
ReturnResultCodeString = "The last run of the task was terminated by the user."

Case TaskResultCodes.SCHED_S_TASK_NO_VALID_TRIGGERS ' &H00041307
ReturnResultCodeString = "Either the task has no triggers or the existing triggers are disabled or not set."

Case TaskResultCodes.SCHED_S_EVENT_TRIGGER ' &H00041308
ReturnResultCodeString = "Event triggers do not have set run times."

Case TaskResultCodes.SCHED_E_TRIGGER_NOT_FOUND ' &H80041309
ReturnResultCodeString = "A task's trigger is not found."

Case TaskResultCodes.SCHED_E_TASK_NOT_READY ' &H8004130A
ReturnResultCodeString = "One or more of the properties required to run this task have not been set."

Case TaskResultCodes.SCHED_E_TASK_NOT_RUNNING ' &H8004130B
ReturnResultCodeString = "There is no running instance of the task."

Case TaskResultCodes.SCHED_E_SERVICE_NOT_INSTALLED ' &H8004130C
ReturnResultCodeString = "The Task Scheduler service is not installed on this computer."

Case TaskResultCodes.SCHED_E_CANNOT_OPEN_TASK ' &H8004130D

ReturnResultCodeString = "The task object could not be opened."
Case TaskResultCodes.SCHED_E_INVALID_TASK ' &H8004130E

ReturnResultCodeString = "The object is either an invalid task object or is not a task object."
Case TaskResultCodes.SCHED_E_ACCOUNT_INFORMATION_NOT_SET ' &H8004130F

ReturnResultCodeString = "No account information could be found in the Task Scheduler security database for the task indicated."
Case TaskResultCodes.SCHED_E_ACCOUNT_NAME_NOT_FOUND ' &H80041310

ReturnResultCodeString = "Unable to establish existence of the account specified."
Case TaskResultCodes.SCHED_E_ACCOUNT_DBASE_CORRUPT ' &H80041311

ReturnResultCodeString = "Corruption was detected in the Task Scheduler security database; the database has been reset."
Case TaskResultCodes.SCHED_E_NO_SECURITY_SERVICES ' &H80041312

ReturnResultCodeString = "Task Scheduler security services are available only on Windows NT."
Case TaskResultCodes.SCHED_E_UNKNOWN_OBJECT_VERSION ' &H80041313

ReturnResultCodeString = "The task object version is either unsupported or invalid."
Case TaskResultCodes.SCHED_E_UNSUPPORTED_ACCOUNT_OPTION ' &H80041314

ReturnResultCodeString = "The task has been configured with an unsupported combination of account settings and run time options."
Case TaskResultCodes.SCHED_E_SERVICE_NOT_RUNNING ' &H80041315

ReturnResultCodeString = "The Task Scheduler Service is not running."
Case TaskResultCodes.SCHED_E_UNEXPECTEDNODE ' &H80041316

ReturnResultCodeString = "The task XML contains an unexpected node."
Case TaskResultCodes.SCHED_E_NAMESPACE ' &H80041317

ReturnResultCodeString = "The task XML contains an element or attribute from an unexpected name space."
Case TaskResultCodes.SCHED_E_INVALIDVALUE ' &H80041318

ReturnResultCodeString = "The task XML contains a value which is incorrectly formatted or out of range."
Case TaskResultCodes.SCHED_E_MISSINGNODE ' &H80041319

ReturnResultCodeString = "The task XML is missing a required element or attribute."
Case TaskResultCodes.SCHED_E_MALFORMEDXML ' &H8004131A

ReturnResultCodeString = "The task XML is malformed."
Case TaskResultCodes.SCHED_S_SOME_TRIGGERS_FAILED ' &H0004131B

ReturnResultCodeString = "The task is registered, but not all specified triggers will start the task."
Case TaskResultCodes.SCHED_S_BATCH_LOGON_PROBLEM ' &H0004131C

ReturnResultCodeString = "The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal."
Case TaskResultCodes.SCHED_E_TOO_MANY_NODES ' &H8004131D

ReturnResultCodeString = "The task XML contains too many nodes of the same type."
Case TaskResultCodes.SCHED_E_PAST_END_BOUNDARY ' &H8004131E

ReturnResultCodeString = "The task cannot be started after the trigger end boundary."
Case TaskResultCodes.SCHED_E_ALREADY_RUNNING ' &H8004131F

ReturnResultCodeString = "An instance of this task is already running."
Case TaskResultCodes.SCHED_E_USER_NOT_LOGGED_ON ' &H80041320

ReturnResultCodeString = "The task will not run because the user is not logged on."
Case TaskResultCodes.SCHED_E_INVALID_TASK_HASH ' &H80041321

ReturnResultCodeString = "The task image is corrupt or has been tampered with."
Case TaskResultCodes.SCHED_E_SERVICE_NOT_AVAILABLE ' &H80041322

ReturnResultCodeString = "The Task Scheduler service is not available."
Case TaskResultCodes.SCHED_E_SERVICE_TOO_BUSY ' &H80041323

ReturnResultCodeString = "The Task Scheduler service is too busy to handle your request. Please try again later."
Case TaskResultCodes.SCHED_E_TASK_ATTEMPTED ' &H80041324

ReturnResultCodeString = "The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition."
Case TaskResultCodes.SCHED_S_TASK_QUEUED ' &H00041325

ReturnResultCodeString = "The Task Scheduler service has asked the task to run."

Case TaskResultCodes.SCHED_E_TASK_DISABLED ' &H80041326
ReturnResultCodeString = "The task is disabled."

Case TaskResultCodes.SCHED_E_TASK_NOT_V1_COMPAT ' &H80041327
ReturnResultCodeString = "The task has properties that are not compatible with earlier versions of Windows."

Case TaskResultCodes.SCHED_E_START_ON_DEMAND ' &H80041328
ReturnResultCodeString = "The task settings do not allow the task to start on demand."

Case Else
ReturnResultCodeString = ""

End Select
End Function
End Module