Sunday, December 30, 2007

Custom RDP solution

In a previous post I covered several issues I ran into with the vista rdp client. I have a few more problems that have pushed me to write my own RDP client. Its more a wrapper around the ActiveX control so it will require RDP 6.0 (and .NET 2.0) to be installed. But it does solve my problems.

Here are the features I want to include

Disable close button
Fix Windows key sticking
Allow saved passwords inside RDP file
Encrypted RDP files
Custom title bar text
Single sign on (v2)

I have every feature working except the single sign on. I am going to bounce that feature to a later release, but I do have a solution worked out for it.

My app can read any RDP file. I only implement a subset of the options, but I can use my existing RDP files. I did implement all the settings that we change. I also added a few new ones that control the new features I added. "disable close button:i:1" and "disable close button message:s:" are 2 examples of added settings.

To disable the close button, I just catch the close event and display a customizable message if the connection is still active. The return them to the form. This solves the issue we have with open record locks on disconnected sessions from people clicking the X to close.

The custom wrapper has the ability to read a password from the rdp file and use it in the connection. This allows me to publish shortcuts with generic usernames and passwords in them again.

I use RDX extensions to indicate encrypted files. I use TripleDES to protect the files from user changes. I have places that I don't want people messing with the settings. Also good for when the password is saved in the file. A custom RDX file editor is provided to my technicians that would need to make changes to the files.

And by default, the Windows key does not stick. That was a nice side effect.
On deployment, I associate RDP and RDX run commands with my custom application. The icon is unchanged and my user experience does not change much.

Saturday, December 29, 2007

Raise events on main form from different thread.

I just saw this post on Nicola Delfino's blog. I run into this same thing that he provides the solution for.

Update WinForm interface from a different thread
Well, this is a typical issue when you have a thread that works (i.e. a Workflow) and a UI that needs to be updated.
Let assume that you have a WinFom and you need to update its windows Title from another thread. The other thread needs to call "UpdateTitle" public method of current Form instance.
In order to obtain that it works you need to implement "UpdateTitle".

See his original post for the code sample:Update WinForm interface from a different thread


More information on Control.InvokeRequired Property on MSDN.

Friday, December 28, 2007

Issues with mstsc rdp 6.0

The change to the new Vista RDP client can be a drastic one. A lot of things have changed. Several changes break existing functionality that many people relied on.
 

Windows key sticking:

If you use the windows key + L to lock your machine while working in a remote session, the windows key becomes stuck. When you return to your session, pressing any key that has a combination with the windows key will activate. E for explorer, L for lock, ect... The solution is to use ctrl +alt +del, or move focus away from the RDP session before locking the computer.
 

Default domain is wrong:

At some point all of our mstsc client forgot what domain they were connected to. Even though my users logged into the local machine with the domain account and tried to remote into a domain terminal server. They tried to authenticate with the machine name as the domain. The solution is to add the domain to the username when typing it in.

Passwords don't save to the rdp file anymore:

You cannot distribute a RDP file with a password. So the user must know the password to use the rdp shortcut.

Login prompt changed:

Not only is it client side, it also has a different look. You can revert it back by adding a special line into the .RDP file: enablecredsspsupport:i:0

 

Can only save one username/password per server:

  This prevents you from creating 2 shortcuts with 2 different usernames to the same server.  I read that you can add a second rdp listening port and point one of those to the new port number to work around this one.



At face value, these look like minor issues.
But the amount of support calls and issues this created in our environment tied us up for a long time. All of them user related. Technology is great when its transparent to the users (and does not generate calls to support staff).

We have a good number of users that cant figure out the difference between username passwords and email passwords. (I cant tell you the number of times we have had someone reboot to fix a problem only to have them call back to tell us they cant get the password to work anymore. ) The change that no longer defaults the domain, the change that sticks the windows key, and the different look of the login screen were all drastic changes to them.

We also have a special application that we deploy using RDP. We create a unique account for each computer that connects to it. We would save the password to the RDP so every user uses the same RDP on that machine. Now we have to have every possible user login, then give us the keyboard to enter the password. Not only is that a huge support drain, its an annoyance to our users. (especially to our remote sites that we visit once every other week).
 
At one point we added a second app to the servers and tried to create a second shortcut for that.  Because the hostname was the same,  it was only keeping the login details of one username.  (we used a different username to access a different published app.)

The sticking window key compounds the problems. Our users don't know the password to that special application RDP connection. So when they get the windows key stuck and press the L key, the remote session gets locked. We get the call and have to remote into that session and enter the password for them.

Our primary solution that works wonders was to upgrade the Vista 6.0 version to the XP 5.2 version. That upgrade fixed all of those problems.

A second solution that I am about done with is to write my own client wrapper for the RDP 6.0 control. We have other issues that I can't solve any other way, and it solves these issues too. I will write up more details on that solution later.

Wednesday, December 26, 2007

Why does my shared clipboard not work?





I used to have an issue where my clipboard would stop working. I would eventualy have to reboot my computer to fix it. I would work with remote desktop alot and the mstsc was the cause of my problems. That write up explains exactly how the clipboard would lock up.




I uncovered a program that would show me the process containing the lock and allow you to kill it. I would love to link that program, but I cant track it down. I did find another one that a microsoft MVP posted that I think does the same thing. Here is a copy of that post.




Hassan,


This has to do with some application that's having a lock over the Windows Clipboard. David Candy's application should determine the Process that's causing the problem. Download GetOpenClipboardWindow.zip from here: http://windowsxp.mvps.org/temp/GetOpenClipboardWindow.zip Unzip and run the tool. Post back what it reports. For best results, run this utility during the time you encounter the Copy<=>Paste problem.




-- Ramesh, Microsoft MVP


Windows XP Shell/User




In the end, upgrading to the RDP 6.0 client also fixes this problem in mstsc. Now that I have upgraded it, I no longer have this issue.

Monday, December 24, 2007

No accessible 'Main' method with an appropriate signature was found

I was working in a project that was a windows application, but I decided to change it to a console application instead. I changed the type to console and selected Sub Main as my startup object. When I compiled it I got the fallowing error: Error 54 No accessible 'Main' method with an appropriate signature was found.

I expected problems because I knew I did not have the Sub Main defined in my project. I added a class file and entered the fallowing code:

Public Class Main

Public Shared Sub Main()

Application.Run(New Form1)

End Sub

End Class




Several examples I saw had main defined with (ByVal args() As String) as the parameter. One thread on the subject indicated that you can add a module instead of a class. Here is the syntax for that.

Module Main

Public Sub Main(ByVal args() As String)

Application.Run(New Form1)

End Sub

End Module




You will notice that the shared keyword is left off inside the module.

Saturday, December 22, 2007

Mapping a drive with custom name

When mapping a drive, the full path of the share is displayed as the name of the drive. We have some very long path names and didn't want our users to see the details they didn't need. So I had to write a script to give those drives custom names.

Our login scripts contained lots of net use statements in *.BAT or *.CMD files. The command looked like this:
NET USE H: //server/share/directory1/directory2/directory3


and it would show in explorer like this:
directory3 on //server/share/directory1/directory2 (H:)


My goal was to make a script just as easy to use at NET USE. That is exactly what I ended up with. The new command looks like this:
Map.vbs H: //server/share/directory1/directory2/directory3 "My directory3"


and it would map like this:
My directory3 (H:)

By making the command simple the scripts are easy to read and modify by others on my team. Now that you know what we are doing, lets take a look at the code needed to make this work.

We are going to save this code with the name Map.vbs. Its a VBScript file executed by the windows scripting host. To make the script more usable, it needs to read the variables from the command line. The first 3 lines populate the variables we will use later.

strDriveLetter = WScript.Arguments(0)
strPath = WScript.Arguments(1)
strDisplayName = WScript.Arguments(2)


The next 2 lines I am going to show you display how to map the drive.

Set objNetwork = CreateObject("WScript.Network")
objNetwork.MapNetworkDrive strDriveLetter, strPath, true


This will map the path strPath to the drive strDriveLetter. The true as the 3rd param is the same as using the /PERSIST param of NET USE. It saves the mapping in the profile.

Now we need to change the name of the mapping. This takes an existing share by drive letter and gives it a custom display name. This was one of the hardest pieces of code to find. The next 2 lines do just that.

Set oShell = CreateObject("Shell.Application")
oShell.NameSpace(strDriveLetter & "\").Self.Name = strDisplayName


If you put that all together you have a bare bones script to map a drive and rename it from the command line via a VBScript. In my next post, I plan on going more in depth into my map.vbs. I will show you how to check for an existing mapping on that letter and all the validation I added.

Tuesday, December 18, 2007

.NET accessing embeded files in your project

I had some files that I wanted to embed into my project and access in code. I needed both images and xml files.



First you have to add the files to the project. I create the images in paint before importing them. The xml files were created within Visual Studio. Things started to look a little cluttered, so I created folders in the project to group them up.



Once the files are in place you need to open the properties for each one. Change the build action to "Embedded Resource". Now we can access the file streams from code.




image warningImage = new Bitmap(this.GetType().Assembly.GetManifestResourceStream("Application1.images.warning.bmp"));


StreamReader xmlFile = new StreamReader(this.GetType().Assembly.GetManifestResourceStream("Application1.foldername.template.xml"));


GetManifestResourceStream returns a stream. There is a overload for the bitmap constructor that takes a stream if its an image you are working with.

Friday, December 14, 2007

NullReferenceException raising an event in C#

I was working with events in C# and ran into this exception. System.NullReferenceException was unhandled Message="Object reference not set to an instance of an object." This exception was at the exact point in my code where I attempted to raise the event. It turns out that if there are not event handlers attached to your event, an attempt to raise it will give you this exception. To solve it, you have to check to see if its null before raising it.


public class TestEvent{
public event EventHandler MyEvent;
public void RaiseMyEvent(){
if(MyEvent != null){
MyEvent(this, null);
}
}
}


All the samples I was looking at did not check for a null value, so neither did I. Now that I had the problem, I see why I need to check.

Sunday, December 09, 2007

Running vbs from .net code

There are a few ways to run vbscript inside vb.net. If you just need the script to run, you can use the process object.


Dim oProcess as new Process
Try
oProcess.StartInfo.UseShellExecute = True
oProcess.StartInfo.FileName = "CScript c:\script.vbs //nologo"
oProcess.Start()
oProcess.WaitForExit()
Finally
oProcess.Dispose()
End Try

Process Class

If you have some VBScript that you want to intereact with variables or run on the fly. Microsoft has a ScriptControl that allows you to do just that. The script control has 3 options of running code.
  • Eval: Evaluates a text expression.
  • Run: Runs a named Sub or Function.
  • Execute: Executes a script statement.

You can find more information on the ScriptControl at Microsoft.
Q184740: How To Call Functions Using the Script Control
Q184739: INFO: Where to Obtain the Script Control


If you are looking to run vb.net code inside vbscript, see my other post:
Running VB.NET code in VBScript or other apps

Friday, December 07, 2007

Raising an event in C#

Here is the simplest way to raise an event.


public class TestEvent{
public event EventHandler MyEvent;
public void RaiseMyEvent(){
MyEvent(this, null);
}
}


This uses the default EventHandler that returns a object source and a EventArgs ex. If you want to pass back different parameters, you will need to create your own delegate. The delegate we just used looks like this:

public delegate void EventHandler (object source, EventArgs ex);



We dont have to define it because .NET already defined it for us. Here is a second example where we built a custom delegate and raise it.


public delegate void MyCustomEventHandler (string message);

public class TestEvent2{
public event MyCustomEventHandler MyEvent2;
public void RaiseMyEvent(){
MyEvent2("Message sent with event");
}
}


Now that I explained how to raise events, there is a bug in that code. I'm saving that for my next post: NullReferenceException raising an event in C#

Saturday, November 17, 2007

Inconsistent accessibility: base class is less accessible than class

I was doing some coding in c#. I had one class but decided to split it up so I could reuse some of the functionality later. So I made a simple shell and started moving pieces into the parent class.

On my first compile I got this message: Inconsistent accessibility: base class is less accessible than class

I am a little rusty with my C# and classes, but it turns out I left off the public keyword on the new class. Adding public fixed that compile error.

Tuesday, November 13, 2007

Mapped drives missing in explorer.

We ran into a problem with our mapped drives on our network. First time logins failed to display in explorer until they logged in the second time. You can see them from a command or dos prompt. The disconnect drives dialog also showed the mappings. I can also kill the explorer.exe process and when I run it from the task manager, the mapping show up.

Running the logon scripts manually will make the drives show up. It looks like a timing issue. If my script maps a lot of drives, the earlier ones are the ones that don’t show up. Any that map after explorer loads work fine. It did not matter if it was the users individual logon script or the group policy log on script. Different logon scripts didn’t fix it either. I have a custom vb script that I use, but I also tested it with a batch file full of NET USE commands.

I have worked on this issue for a long time. Searching different ways and posting to various forums. Finally Lanwench [MVP - Exchange] responded to my problem on microsoft’s technet newsgroup on Windows XP Networking. His solution was to enable the “Always Wait for Network” GPO setting and linked me to the detailed reason for that.

Always wait for the network at computer startup and logon

Administrative Template: System
Policy Node: MACHINE
Policy Path: Administrative Templates\System\Logon
Supported On: At least Microsoft Windows XP Professional or Windows Server 2003 family

Help/Explain Text: Determines whether Windows XP waits for the network during computer startup and user logon. By default, Windows XP does not wait for the network to be fully initialized at startup and logon. Existing users are logged on using cached credentials, which results in shorter logon times. Group Policy is applied in the background once the network becomes available. Note that because this is a background refresh, extensions such as Software Installation and Folder Redirection take two logons to apply changes. To be able to operate safely, these extensions require that no users be logged on. Therefore, they must be processed in the foreground before users are actively using the computer. In addition, changes that are made to the user object, such as adding a roaming profile path, home directory, or user object logon script, may take up to two logons to be detected. If a user with a roaming profile, home directory, or user object logon script logs on to a computer, Windows XP always waits for the network to be initialized before logging the user on. If a user has never logged on to this computer before, Windows XP always waits for the network to be initialized. If you enable this setting, logons are performed in the same way as for Windows 2000 clients, in that Windows XP waits for the network to be fully initialized before users are logged on. Group Policy is applied in the foreground, synchronously. If you disable or do not configure this setting, Windows does not wait for the network to be fully initialized and users are logged on with cached credentials. Group Policy is applied asynchronously in the background. Note: If you want to guarantee the application of Folder Redirection, Software Installation, or roaming user profile settings in just one logon, enable this setting to ensure that Windows waits for the network to be available before applying policy. Note: For servers, the startup and logon processing always behaves as if this policy setting is enabled.

Registry Settings: HKLM\Software\Policies\Microsoft\Windows NT\CurrentVersion\Winlogon!SyncForegroundPolicy

Friday, November 02, 2007

Christmas Wish List

People keep asking for a wish list for Christmas. I'm a hard person to buy for sometimes. I usually don't need very much. When I do see something I want, I have a hard time not buying it early.

I recently purchased a Nintendo Wii. That does open a lot of options for gifts. I made a wish list on amazon that contains the Wii stuff I want.

My Wish List

Each game adds different value to the Wii.

Super Mario Galaxy: Every Nintendo has a Mario platform game that pules you into the system. Its a showcase of design and interaction with the system. This game it exactly that. Mario is new every time its released and becomes a must have game.

Mario Party 8: With the focus of the Wii around group interaction and fun, this game builds on that even more. With a lot of mini games and the option to play 4 people at once, your group will have lots of fun on this one.

Metroid Prime 3 Corruption: Another household Nintendo name that never lets you down. This is a highly interactive first person single player adventure.

Super Smash Bros. Brawl: to be released just after Christmas, its a arcade fighting style game. It pulls in all the classic characters from several popular titles and puts them in the ring against each other.

Super Monkey Ball Banana Blitz : A Sega release that places you in control of a glass ball. You tilt and turn the ground under the ball to control its movements. sounds like its easy to pick up and learn. The difficulty gets insane by the time you finish.

Friday, October 19, 2007

Stop or Disable Terminal Server Logons (Change Logon)

Here is a simple command that I had a hard time finding.

Change Logon /Disable
Change Logon /Enable

It prevents users from logging on to the server that the command was ran on. All new connections will be disabled. Existing connections will not be disconnected. So you can run this with remote users logged in to stop new connections while you wait for people to exit.

You can run that from a terminal session. If you get disconnected, you will have to get physical access to the machine to activate it.

From the website: Enables or disables logons from client sessions, or displays current logon status. This utility is useful for system maintenance.

http://technet2.microsoft.com/WindowsServer/en/Library/15a2f502-1fa3-4d3b-a74b-cec04ee52d061033.mspx?pf=true

Running VB.NET code in VBScript or other apps

Here is how you can write a .NET component and use it in a script. First, create a library that contains the code you want to run.

Public Class FileWriter
Public Sub Save(ByVal PathAndFileName As String)
Try
Dim FileStream As New System.IO.StreamWriter(PathAndFileName, False)
FileStream.WriteLine("Hello World")
FileStream.Flush()
FileStream.Close()
FileStream.Dispose()

Catch e As Exception
Dim fs As New System.IO.StreamWriter("C:\error.txt", True)
fs.WriteLine("Exception: {1}", 1, e.Message)
fs.Flush()
fs.Close()
fs.Dispose()
Throw e
End Try

End Sub
End Class


Then compile the library and run regasm c:\project\bin\NameOfDLL.dll /CODEBASE to register it. Regasm is in your .net folder. That will register the dll so you can use it later. Regsvr32 is for non .NET dlls. Your script is next. Save it as a .vbs file.

dim fs
set fs = CreateObject("Project1.FileWriter")
fs.save "c:\file.txt"
set fs = nothing

Anyplace you can call CreateObject, you should be able to use your class. VBA in office is one such place. And I did not test this code, I just gutted another project to have some examples.

Related: Running vbs from .net code

mscorlib: Request for the permission of type Version Culture PublicKeyToken failed.

I wrote a little database access code in vb.net that I wanted to use in another script. I was getting this error though. C:\Test.vbs(6, 1) mscorlib: Request for the permission of type 'System.Data.OleDb.OleDbPermission, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.

My dev machine worked fine. And the other box looked like it had the data access and .NET components I needed. Looking closer, it was talking about permissions. The connection string was the same and it contained the password. I decided it was a security zone messing me up.

When I ran regasm, the dll was located on a network share. That was about the only difference. So I unregistered it, moved it locally, and ran regasm again. The problem went away.

mscorlib: Request for the permission of type Version Culture PublicKeyToken failed.

Thursday, October 18, 2007

The system cannot find the file specified. CreateObject

I was attempting to use some vb.net code in a vbs file and was getting "The system cannot find the file specified." on the CreateObject line. I tracked it down to the way I registered the DLL.

One sugestion told me to unregister and re register it with regsvr32. Because I was using .NET, I knew that was not it. It turned out to be my use of regasm to register my DLL. I left out the /CODEBASE option.

Once I registered it with that option, the error went away.

Monday, June 25, 2007

Invalid postback or callback argument.

I was tring to move some web apps to a demo server and ran into this error message on one of them.

Invalid postback or callback argument. Event validation is enabled using in configuration or <%@ Page EnableEventValidation="true" %>in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

what made this move interesting is that the page is a aspx page that was converted from a asp page some time ago and that it was still configured as a dot net 1.1 app in iis. When I moved it, i saw the aspx ending and set it us as a dot net 2.0 app. Changing it back fixed my issus.

I did find this link to a related issue and would have tried those steps if my trick did not work.
http://www.netnewsgroups.net/group/microsoft.public.dotnet.framework.aspnet/topic637.aspx

Friday, April 20, 2007

BACKUP LOG cannot be performed because there is no current database backup

I was getting this message when trying to use a script to back up my database: "BACKUP LOG cannot be performed because there is no current database backup". My searching lead me to this link:http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=243166&SiteID=1. where someone else was having the same issue.

The problem happens when you change the database to simple to truncate the log, then back to full recovery. You have to do a manual full backup before you can do a log back up again.

I had gone to simple recovery in an attempt to shrink a log file that was not being maintained.

Migrating SQL Reporting Services 2005

I'm in the process of migrating our production report server and databases into a Virtual PC client to create a Demo machine. This took a lot more steps than I expected.

I installed SQL and Reporting Services. I then tried to back up and restore the report server database and was not having any luck. I later discovered that I need to move both the report server database and the report server temp database.

I used the a script found on MSDN titled Moving a Report Server Database to Another Computer . It looks like it would be found in the SQL Server 2005 Books Online.

This script only works on sql server 2005. There are some new keywords used that 2000 does not support. One key detail was not to use the GUI to back up and restore the databases. Either use a script or detach/reattach the files. The article explains this.

In the configure report server gui, I had to change the credentials type. The default was not working for me. I set it to windows and entered in the account info. connected and applied the changes. If you entered in the wrong password, you will not be alerted to the mistake.

Next step was to restore the encryption key. You will have to export it from the first report server by using the same tool. I was getting this error on the restore: "The encrypted value for the "LogonCred" configuration setting cannot be decrypted". My problem was leaving the password blank on the database set up section.

Once I placed a password in there and gave that account access to the databases, it worked for me.

Another issue I was having was getting the report server initialized. Adding the encryption key solved that.

I then moved over my content, fixed permitions, and changed the datascources. EXEC sp_change_users_login 'Update_One', 'username', 'username' can be an important command to fix logon accounts after a database migration.