Monthly Archives: March 2011

Windows authenticated sysadmin, the painless way


Personally, I hate having a dedicated administrative account, different from the one I normally use to log on to my laptop, read my email, write code and perform all the tasks that do not involve administering a server. A dedicated account means another password to remember, renew periodically and reset whenever I insist typing it wrong (happens quite frequently).

I hate it, but I know I cannot avoid having it. Each user should be granted just the bare minimum privileges he needs, without creating dangerous overlaps, which end up avoiding small annoyances at the price of huge security breaches.

When I was working as a developer only, I was used to having my windows account registered as sysadmin on my dev box and, when I switched to a full time DBA role, it took me a while to understand how important it was to have a different sysadmin user for the production servers.

That said, one of the things that makes the use of dedicated administrative accounts awkward and frustrating is windows authentication in SSMS. While extremely handy when the user that has to log on to the database is the same logged on to windows, integrated security becomes pesky and uncomfortable when the database user is a different one.

No big deal, but launching SSMS as different user brings in some small annoying issues:

  1. SSMS must be opened choosing “Run as…” from the context menu.
    It’s the most common way to run a program as a different user, but I would happily live without this additional step.
  2. The user’s credentials have to be typed in.
    OK, seems trivial, but I find it annoying. Typically, users with elevated privileges are subject to more stringent password policies, that means longer passwords, no dictionary words, symbols. Having to type such a password once a day is enough for me.
  3. No drag & drop from other windows: neither files, nor text
    This limit is imposed by windows, that filters the messages between processes in different security contexts.
  4. Whenever there is more than one instance of SSMS running, it’s impossible to predict which one will open a file on double click
    It’s like russian roulette. Want to play?
  5. Settings are stored separately. Each modification to program settings has to be made on both profiles.
    Application settings are stored somewhere under the user profile folder, or in the registry. In both cases, each user has different settings, stored in different locations.
  6. Save and load dialogs point to different folders
    By default, SSMS points to the user’s documents folder.

How to overcome these annoyances? A simple solution comes from a small tool released from Sysinternals in January 2010.

Desktops

There are dozens, maybe hundreds of applications that allow windows users to create virtual desktops, similar to those found in Linux, but Desktops is different. To say it by Mark Russinvich’s (blog|twitter) words:

Unlike other virtual desktop utilities that implement their desktops by showing the windows that are active on a desktop and hiding the rest, Sysinternals Desktops uses a Windows desktop object for each desktop. Application windows are bound to a desktop object when they are created, so Windows maintains the connection between windows and desktops and knows which ones to show when you switch a desktop.

In other words, Desktops is able to create a whole desktop process and then run new windows bound to that process. This also means that the main desktop process (explorer.exe) can be started in a different security context, simply terminating and restarting it. All the windows started from that moment on will be bound to their originating desktop process, hence to the same security context.

Let’s see how this can be achieved:

  1. Download and install Sysinternals Desktops
  2. Open task manager, find the process named “explorer.exe” and note down its PID
  3. Create a new desktop and activate it
  4. Open task manager, find and kill the explorer process that has a PID different from the one you noted down
  5. From task manager, start a new process: “runas /user:somedomain\someuser explorer.exe”

Done! A new explorer process will be started with the credentials you supplied.

Smooth, isn’t it? Well, not much, still too complex for me:

  • The PID from the original explorer process has to be noted down before creating the new desktop: thereafter it will be impossible to determine which desktop belongs to a process
  • By default, windows restarts automatically explorer whenever it is killed, making our efforts in vain.

In order to work around these problems, I coded a small C# application called RestartExplorer that identifies the explorer process bound to the current desktop and restarts it as a different user.

The code is straightforward and you will find it attached to this post. For those not so comfortable with Visual Studio, I also attached a compiled version.

The core functionality consists of just a few rows of code:

// Create a ProcessStartInfo object to pass credentials
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
if (!username.Equals(""))
{
    fixed (char* pChars = password.ToCharArray())
    {
        pass = new System.Security.SecureString(pChars, password.Length);
    }
    psi.UserName = user;
    psi.Password = pass;
    if(!domain.Equals(System.Environment.MachineName))
        psi.Domain = domain;
}

//Runs the Explorer process
psi.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"); // c:\windows\explorer.exe

//This has to be set to false when running as a different user
psi.UseShellExecute = false;
psi.ErrorDialog = true;
psi.LoadUserProfile = true;
psi.WorkingDirectory = "c:\\";

try
{
    //kill current explorer process
    IntPtr hWnd = FindWindow("Progman", null);
    PostMessage(hWnd, /*WM_QUIT*/ 0x12, 0, 0);
    //start a new explorer with the credentials supplied by the user
    System.Diagnostics.Process.Start(psi);
}
catch (Exception e)
{
    throw e;
}

Once run, the application simply asks for the credentials of the users that will run explorer.exe bound to the current desktop:

By clicking OK, the explorer process gets terminated and immediately restarted under the specified security context. This creates a brand new desktop process, entirely dedicated to our administrative account.

To switch back to the regular users’ desktop, you just have to press the hotkey combination you set up in Desktops’ control panel or click on the tray icon, thus implementing something very similar to windows’ “quick user switch”, but quicker and more versatile.

Desktops tray panel

Desktops control panel

So far we have solved our main issues:

  1. SSMS can be started normally, by double clicking its icon
  2. No credentials to type
  3. Drag & drop allowed from any window in this desktop
  4. SSMS opens the .sql files double clicked in this desktop

Sharing settings

We still have to find a way to share application settings between different windows accounts.

We spent hours and hours configuring SSMS with our favourite keyboard settings, templates and all the other things that make our lives easier: we don’t want to set up everything from scratch for our administrator user. Is there a way to share the same settings we set up for the regular user?

Of course there is, and, again, it comes from Sysinternals and it is named Junction.

Junction is a tool that allows creating symbolic links on the NTFS file system. The concept of symbolic links has been present for many years in UNIX operating systems: symlinks are anchors to files or folders residing on different paths in the file system, that are treated as if they were physical files or folders in the path they are linked in.

In order to share settings between two users, we could create a symbolic link from the administrator user’s profile folder to the regular user’s profile folder, as represented in this picture:

Unfortunately, the profile folder contains a special file, named NTUSER.dat (the user’s registry keys), that cannot be opened concurrently on a desktop operating system.
The only possible solution is linking each subfolder in the profile path:

A quick and easy way to accomplish this task is running this script:

Set fs = createObject(“Scripting.FileSystemObject”)
Set ws = createObject(“WScript.Shell”)

JunctionPath = “d:\Downloads\Junction\Junction.exe”

For each dir in fs.getFolder(“.”).subFolders
	DestPath = “..\AdminUser”
	If NOT Fs.folderExists(DestPath) Then
		Fs.createFolder(DestPath)
	End if
	If NOT Fs.folderExists(DestPath & “\” & dir.name) Then
		call ws.run(JunctionPath & “ “”” & DestPath & “\” & dir.name & “”” “”” & dir.path & “”””, 0, true)
	End if
Next
MsgBox “Profile linked successfully!”, vbInformation

Instructions:

  1. Copy the script code and save it as createJunction.vbs in the profile folder the links will point to (For instance, “c:\documents and settings\RegularUser”)
  2. Update “DestPath“ with the name of the user that will link the profile (For instance, “AdminUser”)
  3. Update “JunctionPath” with the path to Junction.exe
  4. Create a backup copy of the profile we will substitute with the links
  5. Delete all the folders in the admin profile, but keep all the files (especially NTUSER.dat)
  6. Go back to the folder where you saved the script and run it

For each subfolder in the regular user’s profile, the script will create a junction in the administrator user’s profile folder. This will not be directly visible from explorer: junctions are not different from real folders. Running DIR from the command prompt, instead, will reveal that we are dealing with something completely different:

WARNING!!  Symbolic links act exactly as normal folders: this means that deleting a folder that is instead a symbolic link, will delete the link’s target folder. In other words, deleting a file from AdminUser’s profile folder, will delete the file from RegularUser’s profile! Be careful!


What we achieved is a complete sharing of settings between our users. Going back to our original issue list:

  1. Settings are saved in the same path. Every change in the program settings will be automatically saved in the original profile.
  2. Open and save dialogs point to the same documents folder.

Mission accomplished!

However, registry settings will not be shared. There is a tool (RegLN) that allows creating symbolic links in the registry, but, personally, I don’t feel like exploring this possibility, that I find a bit dangerous.

In the end, SSMS settings are saved in the profile folder, which we already have shared. This is enough for me.

RESOURCES:

Oracle BUG: UNPIVOT returns wrong data for non-unpivoted columns


Some bugs in Oracle’s code are really surprising. Whenever I run into this kind of issue, I can’t help but wonder how nobody else noticed it before.

Some days ago I was querying AWR data from DBA_HIST_SYSMETRIC_SUMMARY and I wanted to turn the columns AVERAGE, MAXVAL and MINVAL into rows, in order to fit this result set into a performance graphing application that expects input data formatted as {TimeStamp, SeriesName, Value}.

Columns to rows? A good match for UNPIVOT.

Oracle 11g introduced PIVOT and UNPIVOT operators to allow rows-to-columns and columns-to-rows transformations. Prior to 11g, this kind of transformation had to be coded with bulky CASE expressions (for PIVOT) or pesky UNION queries (for UNPIVOT). PIVOT and UNPIVOT allow developers to write more concise and readable statements, but I guess that not so many people have been using these features since their release, or they would have found this bug very soon.

Here is the statement I was trying to run:

WITH Metrics AS (
    SELECT to_date(to_char(BEGIN_TIME,'YYYY-MM-DD HH24'),'YYYY-MM-DD HH24') AS TS,
        AVG(AVERAGE) AS AVERAGE,
        MAX(MAXVAL) AS MAXVAL,
        MIN(MINVAL) AS MINVAL
    FROM DBA_HIST_SYSMETRIC_SUMMARY
    WHERE METRIC_NAME = 'Host CPU Utilization (%)'
    GROUP BY to_date(to_char(BEGIN_TIME,'YYYY-MM-DD HH24'),'YYYY-MM-DD HH24')
)
SELECT TS, aggregate, value
FROM Metrics
UNPIVOT (value FOR aggregate IN (AVERAGE, MAXVAL, MINVAL))
ORDER BY 1

The idea behind was to convert the date column into a string without the minute part, in order to convert it back to date and group by hour.

Surprisingly enough, this was the result:

Wrong results with UNPIVOT

The date column was returned with wrong data. Why?

The issue seems to be related to the date datatype, because converting back to date after the UNPIVOT works just fine:

WITH Metrics AS (
    SELECT to_char(BEGIN_TIME,'YYYY-MM-DD HH24') AS TS,
        AVG(AVERAGE) AS AVERAGE,
        MAX(MAXVAL) AS MAXVAL,
        MIN(MINVAL) AS MINVAL
    FROM DBA_HIST_SYSMETRIC_SUMMARY
    WHERE METRIC_NAME = 'Host CPU Utilization (%)'
    GROUP BY to_char(BEGIN_TIME,'YYYY-MM-DD HH24')
)
SELECT to_date(TS,'YYYY-MM-DD HH24') AS TS, aggregate, value
FROM Metrics
UNPIVOT (value FOR aggregate IN (AVERAGE, MAXVAL, MINVAL))
ORDER BY 1

This query, instead, produces the expected results.

Correct data with char column

I raised this issue with Oracle Support who filed it under bug ID 9900850.8. Both 11.2.0.1 and 11.2.0.2 seem to be be affected by this problem, but it’s quite unlikely to see it fixed before 12.1.

Time will tell.

A short-circuiting edge case


Bart Duncan (blog) found a very strange edge case for short-circuiting and commented on my article on sqlservercentral.

In my opinion it should be considered a bug. BOL says it clearly:

Searched CASE expression:

  • Evaluates, in the order specified, Boolean_expression for each WHEN clause.
  • Returns result_expression of the first Boolean_expression that evaluates to TRUE.
  • If no Boolean_expression evaluates to TRUE, the Database Engine returns the else_result_expression if an ELSE clause is specified, or a NULL value if no ELSE clause is specified.

What makes Bart’s example weird, is the fact that the ITVF seems to be the only scenario where the ELSE branch of the expression gets evaluated:

-- Autonomous T-SQL batch: everything runs just fine
DECLARE @input int
SELECT @input = 0
SELECT calculated_value =
    CASE
        WHEN @input <= 0 THEN 0
        ELSE LOG10 (@input)
    END
GO

-- Scalar function: runs fine
CREATE FUNCTION dbo.test_case_short_circuit2 (@input INT)
RETURNS int
AS BEGIN
RETURN (
    SELECT calculated_value =
        CASE
            WHEN @input <= 0 THEN 0
            ELSE LOG10 (@input)
        END
)
END
GO

SELECT dbo.test_case_short_circuit2 (-1);
GO

However, short-circuiting should never be something to rely upon: whenever there’s an alternative way to express the statement, I suggest using it.

2011/03/04 UPDATE:

Paul White (blog | twitter) agrees to consider this as a bug:

It is constant-folding at work. If you replace the literal constant zero with a variable, the problem no longer occurs. SQL Server expands the in-line TVF at optimization time and fully evaluates the CASE with the constant values available.
Constant-folding should never cause an error condition (such as an overflow) at compilation time – there have been other bugs in this area fixed for the same reason.

More details on the discussion thread of my article.