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


I have been programming for nearly 30 years now. I started with BASIC on a Texas Instruments TI-99 with voice Synthesizer.
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)

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


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


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


Press F6 to Create a member


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:
This indicates we are using the free form version of code.
Next put the following:
dsply ‘Hello World’;

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


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:
Now let me break it down for you.
This creates a save file in the library BACKUP we created earlier called HTEDTA.
This clears the save file in the library BACKUP called 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:

mget *
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.
Also thanking IBM for the Hello World instructions.!/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()
End Sub
Private Sub mUndo()
End Sub
Private Sub mRedo()
End Sub
Private Sub mCut()
End Sub
Private Sub mCopy()
End Sub
Private Sub mPaste()
End Sub
Private Sub mReplace()
End Sub
Private Sub mSelectAll()
End Sub

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

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

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

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

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

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

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

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

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

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

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
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:


First two are VARCHAR, with the rest being TINYINT.


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 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.

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.

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

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."

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."

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

Random Number from a Group of Numbers

While VB.Net has a few different ways to generate a random number, what I need was a way to generate a random number picked from a list of numbers. For example given the following set of numbers:

1, 3, 5, 8, 12, 15, 21, 23, 35, 46, 51

Now VB.Net’s Random.Next allows for a range of numbers but that’s not what I needed. The solution to this was actually quite easy.

'Add this to the declarations area of your class
Private _random As New Random()

Next paste this function into your code


''' Generate a random number given a set of numbers to use

''' How many numbers to return as once ''' List of numbers to get the random numbers from ''' ArrayList of the numbers
Function GenerateRandomNumber(NumberToReturn As Integer, NumbersToUse As ArrayList) As ArrayList
'Create a new arraylist to hold the numbers
GenerateRandomNumber = New ArrayList

Dim NumberOfNumbers As Integer = NumbersToUse.Count
Dim NewNumber As Integer

'Do this as many times as the user needs
For i = 1 To NumberToReturn
Do While GenerateRandomNumber.Contains(NumbersToUse(NewNumber))
'Get a new number
NewNumber = (_random.Next(0, NumberOfNumbers))

'Sort the new list of numbers
Catch ex As Exception
GenerateRandomNumber = Nothing
End Try

End Function

Lastly call the function with a list of the numbers you want to generate a random number from and the number of random numbers to return

Dim GenerateNumberList as arraylist
GenerateNumberList = GenerateRandomNumber(6, NumbersList)

Setting the Monitor your Application Appears on

Since I now have four monitors, I thought it would be a good idea in one of my applications to save the position and monitor my app last ran on. Looking around the web I did not see much info on this so I thought I would share my findings.

Basically you need just a couple of lines of code to make this work.

First I created a new setting and called it “Screen” I defaulted this to 1

Next I added the following to my form closing event:
Dim sScreen As System.Windows.Forms.Screen = Screen.FromControl(Me)
My.Settings.Screen = Val(sScreen.ToString.Last)

Then this to my forms load event:

'Make sure the user did not save the location to a monitor that no longer exists
If My.Settings.Screen > Screen.AllScreens.Count Then My.Settings.Screen = 1
'Set the monitor to whcih the app will appear.
Me.Location = Screen.AllScreens(My.Settings.Screen - 1).Bounds.Location

Pretty easy huh?

Selecting Rows in a ListView Control

Interestingly enough I had a need to select all the rows in my list view control but there wasn’t a method to simply do this. I expected something like:
Neither methods are available so the only way to get the items selected was to loop through the items one at a time and set the items selected property to true. Thus the resulting code was used:

For Each i As ListViewItem In lv_Results.Items
i.Selected = True

Now the whole reason for this post is that one line of code:

You see when I was researching the mystery of why the aforementioned methods were not available I came across an old post where someone was asking how to select all items in the listview.

After someone had given him code similar to mine, he asked why the rows were selected in gray. The answer was really simple, but for some reason no one had answered him. So for posterity’s sake I thought I should mention it here. In his case he was clicking a button to “add all rows to selection” thus removing the focus away from the listview control. The resulting default behavior is to turn the selection gray. Sounds obvious but it could be a head scratcher…

Maybe this will help another programmer…