Monday, January 19, 2004

How To Disable Browser HotKeys Using JavaScript

I noticed that a number of people were getting to my blog searching at Google for how to disable web browser hotkeys, so I figured I'd make them happy by writing a blog showing how to do this.

NOTICE: I saw in my logs that some people searching for disabling CTRL-P were sent to this page by Google. If you're interested in disabling printing from a web page using JavaScript, see this blog entry instead.

First of all, let me say that disabling hotkeys in a browser is by no means a certain prospect using javascript. I have never had success disabling the alt-keys for built-in browser menu items, and the way the browser handles the hotkeys tends to vary from browser to browser. So...

NOTICE: Do not rely on JavaScript attempts to disable hotkeys as an iron-clad security measure! There's no guarantees!

However, that said, here's a method that works in IE 4 and above. I have the code setup for Netscape as well, just in case you want to use the code to trap keys in Netscape and IE (but they will only be successfully disabled in IE).

function keyTrap(e)

{
// Netscape uses e, IE window.event
var eventObj = (e)?e:window.event;
var keyPressed = (e)?e.which:window.event.keyCode;
var ctrlPressed = (e)?(e.ctrlKey):window.event.ctrlKey;

if (ctrlPressed)
{
switch (keyPressed)
{
case 80: // CTRL-P.
window.event.keyCode = 0;
return false;
}
}
}
if(document.layers)
{
//NS4+
document.captureEvents(Event.ONKEYDOWN);
}
document.onkeydown=keyTrap;


-

This code disables only CTRL-P, which can be useful in preventing printing of a page. It doesn't work at all if the user has Netscape. The key is trapped, but the Print dialog still comes up. Even if you try to set e.which to 0, it still does not work.

SOAPBOX: Herein lies one of the most frustrating aspects of building web applications. You have to have six copies of various versions of browsers available if you want 100% compatability. ASP.NET promised it would take care of this problem, and it does a very good job at doing so for its built-in components. However, despite ASP.NET's greatly expanded capabilities over ASP, I have yet to create a web project that does not need at least some javascript coding. And Microsoft and Netscape bicker and fuss over who's version of javascript is correct. Microsoft is notorious for adding in their own functionality without anybody else's approval, and Netscape is notorious for not duplicating it in the same way (even if they think it's a great idea--it's like they give a property its own name in javascript that is different from the one Microsoft gave it--even if it does exactly the same thing!--just so it will be different from Microsoft).

Okay, off the soapbox.

That's it for today. Feel free to email me with any questions or comments.

Friday, January 16, 2004

Accessing Network Files And Resources From ASP.NET

Accessing network resources through ASP/ASP.NET has never been a terribly simple task, not if you want to maintain a high level of security for your server.

Despite not being as simple as I'd like, it is not difficult to do. Microsoft's recommendation comes in the following steps:

  1. Create an assembly that works with the network resources.
  2. Add the assembly to Component Services (COM+).
  3. Set the user the COM+ object runs under to one with rights to the network resources.
  4. Add a reference to your COM+ assembly to the web project and access the network resources using it.


I'm going to show you step by step how to do these things. It really isn't difficult, but breaking up your network resources library into a seperate project does fragment things a bit.

1. Create an assembly that works with the network resources.

This is pretty simple. Here's some example code:

Imports System

Imports System.Reflection
Imports System.EnterpriseServices

<Assembly: AssemblyKeyFileAttribute("example.snk")>
<Assembly: ApplicationActivation(ActivationOption.Server)>
<Assembly: ApplicationName("NetworkResourcesExample")>
Public Class AccessNetworkResources
Inherits ServicedComponent

Public Function ExampleFunction() As String

' Put your code that access the network resources here.

End Function

End Class


---
An assembly that will go into Component Services is not much different than any other assembly. All you have to do is have your class inherit from ServicedComponent and add a few properties. The AssemblyKeyFileAttribute property gives the name/path of the key pair file that will be used to strong name the assembly. To create a new key pair file, use the sn.exe utility like so:
sn -k example.snk

---
The ApplicationActivation property tells COM+ to run the application as a Server (as opposed to a Library). You need to do this so that you can assign a different user for the assembly to run under other than the caller. The third property, ApplicationName, tells COM+ the name to list your assembly under in the component services manager (also, if you use any legacy code to access the object, such as classic ASP, you can call the object using CreateObject("ApplicationName.ClassName")).


2. Add the assembly to Component Services (COM+).
This is a very simple step. Once you've compiled the assembly, go the command line and change into the directory where the compiled assembly is located. Then execute the following:
regsvcs [assemblyname]

---
This will install the assembly as a COM+ component.


3. Set the user the COM+ object runs under to one with rights to the network resources.
Now you need to set the COM+ object to run under a user who has rights to the network resources that you need to access. To do this go to: Control Panel -> Administrative Tools -> Component Services. Drill into the Component Services node and into the computer the component is installed on. In the COM+ Applications folder you will find your newly installed component under the name you gave it in the ApplicationName property mentioned earlier.

Right-click on the application and go to properties. In the properties dialog go to the Identity tab. Select either the "Network Service" account under "System Account" or select "This User" and enter the username and password that has access to the resources. I would recommend creating a seperate account that only has access to the specific resources needed, just for safety. Click OK.


4. Add a reference to your COM+ assembly to the web project and access the network resources using it.
This step is pretty self explanatory, but I do have one comment about it. When you add the reference, you don't need to go to the COM tab of the references dialog and select your object from there (in fact, you can't do that, it wont work). You just browse to the compiled assembly you created for the library and select it directly. The .NET framework recognizes that the assembly was installed under COM+ and will activate it using the proper credentials.


That's it! You should be securely accessing the network resources from your web project now.

Thursday, January 15, 2004

Retreiving Performance Counter Values With VB.Net

From Windows 98 on Microsoft has included performance counters in their O/S that measure all kinds of things going on in the system: memory usage, disk access, network bandwidth use, etc. Your software can access these values through the performance counters for reporting, notification of system problems, etc. I've recently finished an application which sits on a server and regularly pulls performance counter values from all of the computers on the network, alerting the network administrator if any of the values rise above of fall below values set as the acceptable range.

It's very easy to access performance counters in Visual Basic .NET. Here's how you do it:


Imports System.Diagnostics

Public Class CounterTest

Public CounterTestValue As Single

Public Sub New()

Dim counter As New PerformanceCounter("Processor", _
"% Processor Time", _
"_Total")

CounterTestValue = counter.NextValue

End Sub

End Class


---

Here's the syntax of the PerformanceCounter constructor:

New PerformanceCounter(CategoryName, CounterName, InstanceName)


---

The category name is which group of counters the counter you want belongs to (such as Processor or Memory or Thread), the counter name is the text name of a counter that is in that category. Those two things are pretty obvious.

The InstanceName is not always so obvious, because it's not always required. A counter's instances break down the counter into all of its smaller parts. For example, the Process category has a counter called "% Processor Time" that has instances for each application currently running, as well as one called "_Total" (a common instance name) which gives you the processor time of all processes. Not all counters have an instance. A few do. If a counter does not have an instance you can pass an empty string, or better yet, just use the constructor overload that does not require the instance name.

The PerformanceCounter class also has a constructor overload which takes the network id of the machine you want to get the counter from. So, for example, if I wanted to get the processor usage on a machine on the LAN called "Loris", I would change the above class like so:


Dim counter As New PerformanceCounter("Processor",
"% Processor Time",
"_Total",
"Loris")


---

It's that simple.

NOTE: Be careful when pulling performance counters from machines on the LAN running different versions of Windows. Microsoft introduced new performance counters with each new Windows version, so make sure that the counter you are able to pull from your Windows XP machine exists on that Windows 2000 box before you try it. Also, the user under which your program is running must have rights to access performance counters on other machines. It is usually best to run the program under a Domain Administrator user, as this guarantees its ability to read the counters.

To find out what performance counters are available to you, go to Control Panel -> Administrative Tools -> Performance. Right-click in the graph and select "Add Counters". There you can see a list of all of the categories and their specified counters and the instances of those counters.

TIP: Many of Microsoft's Software Packages create their own performance counters when installed. A big one is MS Sql Server, which creates dozens of counters for database administrators to use in monitoring the performance of their databases. Your software can access these as well. In addition, .NET makes it very easy to create your own performance counters. How to create your own counter will be the subject of a future blog.

Wednesday, January 14, 2004

Code Generation

In a recent project I decided to explore the idea of code generation, even writing a tool that automatically generated the classes for all the database access to a SQL Server database.

My tool was pretty specific to my needs, and in a later project I found that it just wasn't flexible enough. In a link sent to me I happened to find a great freeware code generation tool created by Eric J. Smith called CodeSmith. It lets you write your own templates for code generation based on tables from any database (OLE DB or ODBC, including Access and SQL Server). I used it to generate dozens of database access classes for one of my team's projects. Saved literally hundreds of hours of coding.

Check it out and let me know what you think.

CodeSmith
http://www.ericjsmith.net/codesmith/

Tuesday, January 13, 2004

Disable Printing Of A Web Page

And still another trick I have learned while working on my current ASP.NET project is how to prevent a user from printing the web page that they are viewing. This is a two step process. The first step is very simple. Add this tag in the HEAD section of your page:

<style media="print">
BODY { DISPLAY: none }
</style>


---

What this stylesheet entry does is tell the browser to replace everything in between the BODY tags of the page with nothing when the user tries to print the page. All the user will get is a blank page.

However, nothing prevents a user from saving the page to his hard drive and editing the style tag out of the page. So much for that security. JavaScript to the rescue! Add this code to HEAD of the page:


// Prevent selecting text for copying.
function deselect(){
if (document.selection) {
document.selection.empty();
Copied=document.body.createTextRange();
}
else if (window.getSelection){window.getSelection().removeAllRanges();}}

timerID=setTimeout('setInterval("deselect()", 1)',1);

// Capture right-click and prevent it to prevent copying text or viewing source.
function clickIE4(){
if (event.button==2){
return false;
}
}

function clickNS4(e){
if (document.layers||document.getElementById&&!document.all){
if (e.which==2||e.which==3){
return false;
}
}
}

if (document.layers){
document.captureEvents(Event.MOUSEDOWN);
document.onmousedown=clickNS4;
}
else if (document.all){
document.onmousedown=clickIE4;
}

document.oncontextmenu=new Function("return false")



---

This code prevents right-clicking and prevents selecting of text in Netscape (and IE). To make absolutely sure that nobody can copy text from the page in IE, add this code to the BODY tag:

<BODY onselectstart="return false;" ondragstart="return false;">


---

That will prevent any selecting of text at all (but it is not supported in Netscape, so you'll need the previous code for that).

Of course, this still does not prevent the user from doing a File->Save on your page. To prevent that, just use javascript to open the page that should not be printed in a new window without the toolbars. This also has the added security of preventing the user from disabling javascript as a trick to get the page contents. If javascript is disabled in the browser, the page will not come up since javascript is used to launch the window.

The final possibility is that the user could enable javascript, open the window, disable javascript while the window is open and then hit F5 to refresh the opened page. With the javascript disabled, the user would be able to copy the page contents.

Now, you can trap the refresh key with javascript, but you cannot prevent refreshing the page. All you can do is preform some task before the page is refreshed. In order to be totally secure, you can have the window close if the user tries to refresh the page. Here's the code (goes in the HEAD tag):


function preventRefresh(e)
{
// Netscape uses e, IE window.event
var eventObj = (e)?e:window.event;

if(eventObj.which)
{
//NS4+
if(e.which==116)
{
alert('No refreshing of this page is allowed.' +
'This window will now close.');
window.close();
}
} else {
//IE
if(window.event.keyCode==116)
{
alert('No refreshing of this page is allowed.' +
'This window will now close.');
window.close();
}
}
}
if(document.layers)
{
//NS4+
document.captureEvents(Event.ONKEYDOWN);
}
document.onkeydown=preventRefresh;



---

Voila! An unprintable, uncopyable secured web page.