Sunday, April 7, 2013

Why I Hate JScript

Let's start off with - yes, we're talking about Active Server Pages (ASP). And no, no one is really using ASP anymore. Yet, for some reason some of us are still dealing with them. So here we go...

I've hated JScript for over a decade. When I first got into web development, ASP was the method of choice for Windows environments. Almost all the examples of code online and in books were written in VBScript. But there were those "special few" who believed that ASP pages should be written with JScript – some strange version of JavaScript. As if it made them feel more like a real programmer, and less of scripter. But let's face it, anything "they" could do in JScript, I could do in VBScript. Aside from try catch blocks, there really wasn't much difference in the languages.

And now that it is literally of no importance, as almost no one is writing ASP pages at this time, I've run across an actual technical reason to justify my disdain for JScript.

A while back I had to make some updates to a very old web application written in ASP using JScript. The application had a function where it did a search for people in Active Directory and displayed the results along with some essential attributes about the people. The search worked fine, but they wanted a new attribute to be displayed. The organization this supported stored an additional phone number about each user in active directory, which was useful to employees and partners. This phone number was stored in the Active Directory attribute called otherTelephone – which is a multivalued attribute. In our case, each person had only one otherTelephone number defined, so I knew I could always pull the first one. This is where this is a located in AD:



I updated the existing code, only to find that the otherTelephone attribute was not being displayed. I pulled the AD search and display code into a separate page to start troubleshooting the problem, but still couldn't find any way to make it work. I've since replicated the problem in my home lab, and here's the code:

<%@ LANGUAGE="jscript" %>
<%
 var oConn = Server.CreateObject("ADODB.Connection");
 oConn.Provider = "ADsDSOObject";
 oConn.Open();

 var oCmd = Server.CreateObject("ADODB.Command");
 oCmd.ActiveConnection = oConn;
 oCmd.CommandText = "<LDAP://DC=company,DC=org>;(&(objectCategory=person)(cn=*smith));givenname,sn,mail,telephoneNumber,otherTelephone;subtree";

 var oResult = oCmd.Execute();
 while (oResult.EOF == false)
 {
  Response.Write("<hr />");
  Response.Write(oResult.Fields("givenname") + " " + oResult.Fields("sn") + "<br />");
  Response.Write(oResult.Fields("mail") + "<br />");
  Response.Write(oResult.Fields("telephoneNumber") + "<br />");
  var otherPhone = oResult.Fields("otherTelephone").Value;
  if (otherPhone != null)
  {
   Response.Write(otherPhone[0] + "<br />");
  }
  oResult.MoveNext();
 }
%>

This code generates the following:


John Smith
john.smith@company.com
555-111-1112
undefined

Jane Smith
jane.smith@company.com
555-111-1111
undefined

The code isn't correctly reading the value from the otherTelephone attribute in AD. If you think that there's something wrong with the code, here's the same code that I rewrote in VBScript.

<%@ LANGUAGE="vbscript" %>
<%
 Set objConn = CreateObject("ADODB.Connection")
 objConn.Provider = "ADsDSOObject"
 objConn.Open

 Set objCmd = CreateObject("ADODB.Command")
 objCmd.ActiveConnection = objConn
 objCmd.CommandText = "<LDAP://DC=company,DC=org>;(&(objectCategory=person)(cn=*smith));givenname,sn,mail,telephoneNumber,otherTelephone;subtree"

 Set objResult = objCmd.Execute
 While Not objResult.EOF
    Response.Write("<hr />")
    Response.Write(objResult.Fields("givenname") & " " & objResult.Fields("sn") & "<br />")
    Response.Write(objResult.Fields("mail") & "<br />")
    Response.Write(objResult.Fields("telephoneNumber") & "<br />")
    otherPhone = objResult.Fields("otherTelephone").Value
    If IsNull(otherPhone) = False Then
       Response.Write(otherPhone(0) & "<br />")
    End If
    objResult.MoveNext
 Wend
%>

This code generates the following:


John Smith
john.smith@company.com
555-111-1112
x7777

Jane Smith
jane.smith@company.com
555-111-1111
x8888

And wouldn't you know it – it works just as it should in VBScript. Suck it JScript.

At the end of the day, rewriting the entire ASP page that I was updating to use VBScript wasn't an option. Remember, the code I've listed here is just part of the original page. The answer to the problem was to add a VBScript function to the existing JScript page. The code for that is listed below and renders the same HTML as the full VBScript code.

<%@ LANGUAGE="jscript" %>

<script language="vbscript" runat="server">
 Function readOtherTelephone(otherTelephone)
  If IsArray(otherTelephone) Then
   readOtherTelephone = otherTelephone(0)
  Else
   readOtherTelephone = Null
  End If
 End Function
</script>

<%
 var oConn = Server.CreateObject("ADODB.Connection");
 oConn.Provider = "ADsDSOObject";
 oConn.Open();

 var oCmd = Server.CreateObject("ADODB.Command");
 oCmd.ActiveConnection = oConn;
 oCmd.CommandText = "<LDAP://DC=company,DC=org>;(&(objectCategory=person)(cn=*smith));givenname,sn,mail,telephoneNumber,otherTelephone;subtree";

 var oResult = oCmd.Execute();
 while (oResult.EOF == false)
 {
  Response.Write("<hr />");
  Response.Write(oResult.Fields("givenname") + " " + oResult.Fields("sn") + "<br />");
  Response.Write(oResult.Fields("mail") + "<br />");
  Response.Write(oResult.Fields("telephoneNumber") + "<br />");
  var otherPhone = oResult.Fields("otherTelephone").Value;
  Response.Write(readOtherTelephone(otherPhone) + "<br />");
  oResult.MoveNext();
 }
%>

JScript is inferior. End of story.

Friday, October 5, 2012

SharePoint 2010: Expired sessions are not being deleted from the ASP.NET Session State database

While there are several things that can cause this warning to appear from the Health Analyzer in Central Administration, I'm assuming you've gotten past the obvious issues - such as making sure the SQL Server Agent is running, as this fantastic TechNet article states. If you're running SQL Express, you're just plain out of luck.

As the warning states, the issue revolves around a SQL job that is supposed to delete expired sessions from the ASP.NET session state database, which is probably missing if you're getting this warning. There are lots of articles out there that describe how to resolve the issue, but I want to share links to a few that I think provide the best information to the cause and resolution.

As for the cause of the issue, we go to a blog post from Bram Nuyts, Enable-SPSessionStateService; Check your SQL permissions. Understanding that the Enable-SPSessionStateService command is trying to create a job under the SQL Server Agent, it's simply a matter of permissions. Following guidance from Microsoft, Initial deployment administrative and service accounts, your setup user account has securityadmin and dbcreator permissions in SQL. His article indicates that you need sysadmin permissions as well for the account that you're using to run Enable-SPSessionStateService. While sysadmin perimssions will do the trick, in reading about permissions for the SQL Server Agent on MSDN, it looks like SQLAgentUserRole should be enough. But I have yet to test that.

A few articles I read when trying to understand this warning seemed to confuse the SharePoint State Service with the SharePoint Server ASP.NET Session State Service. They both have expired sessions that need to be regularly deleted, but they operate differently, timer job vs. SQL job. For a great article on how the two services work, read More Information about Health Analyzer Rules talking about session expirations, by Microsoft PFE sowmyancs.

To resolve the problem, you have to manually create the job under the SQL Server Agent. Dave Pileggi posts steps to do that here. But if you read this forum thread, you'll find a post from Sean Douglas which states that the job must be created such that it meets a specific naming convention. Otherwise, you'll still get a warning from the Health Analyzer. That naming convention is: <state db name>_Job_DeleteExpiredSessions

What I didn't see in the articles I read was information about the schedule for the job. If you're wondering, when the job is created successfully by the Enable-SPSessionStateService command, it is scheduled to run every minute. Better yet, here's the actual SQL script to create the job as it should have been created to begin with. All I did was generate a create script from the job, then make a few tweaks so it's easier to run. Edit the lines towards the top with your session state database name (@SessionStateDB) and your farm account username (@FarmAccount).

USE [msdb]
GO

BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0

DECLARE @SessionStateDB NVARCHAR(200)
DECLARE @FarmAccount NVARCHAR(200)
SET @SessionStateDB = N'Session_State_DB'
SET @FarmAccount = N'DOMAIN\farm_account'

DECLARE @jobName NVARCHAR(MAX), @stepName NVARCHAR(MAX), @scheduleName NVARCHAR(MAX), @scheduleUID UNIQUEIDENTIFIER
SET @jobName = @SessionStateDB + N'_Job_DeleteExpiredSessions'
SET @stepName = @SessionStateDB + N'_JobStep_DeleteExpiredSessions'
SET @scheduleName = @SessionStateDB + N'_JobSchedule_DeleteExpiredSessions'
SET @scheduleUID = NEWID()

IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
BEGIN
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

END

DECLARE @jobId BINARY(16)
EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=@jobName,
@enabled=1,
@notify_level_eventlog=0,
@notify_level_email=0,
@notify_level_netsend=0,
@notify_level_page=0,
@delete_level=0,
@description=N'Deletes expired sessions from the session state database.',
@category_name=N'[Uncategorized (Local)]',
@owner_login_name=@FarmAccount, @job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=@stepName,
@step_id=1,
@cmdexec_success_code=0,
@on_success_action=1,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=1,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'EXECUTE DeleteExpiredSessions',
@database_name=@SessionStateDB,
@flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=@scheduleName,
@enabled=1,
@freq_type=4,
@freq_interval=1,
@freq_subday_type=4,
@freq_subday_interval=1,
@freq_relative_interval=0,
@freq_recurrence_factor=0,
@active_start_date=20001016,
@active_end_date=99991231,
@active_start_time=0,
@active_end_time=235959,
@schedule_uid=@scheduleUID
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:

GO

Friday, June 17, 2011

Install SQL 2008 R2 on Windows Server 2008 R2 Core

Is it possible? Yes. Is it supported? No.

The Hardware and Software Requirements for Installing SQL Server 2008 R2 from Microsoft clearly state that, "SQL Server 2008 R2 is not supported on Windows Server 2008 SP2 Server Core or Windows Server 2008 R2 Server Core installations."

To be clear, this post just covers the installation of the Database Engine Services. Perhaps if I get bored in the future, I'll try Analysis Services or Reporting Services. Reporting Services I'm pretty sure will be more complicated as it requires IIS.

So let's get started. To begin with you need to ensure the list of roles below are installed. If you're using Core Configurator (and I think you should be), some of these roles will already be installed. From the Core Configurator menu, select "Computer settings…" and then "Add or Remove Roles…"
  • MicrosoftWindowsPowershell [is installed by Configurator]
  • NetFx2-ServerCore [is installed by Configurator]
  • NetFx2-ServerCore-WOW64
  • NetFx3-ServerCore
  • NetFx3-ServerCore-WOW64
  • ServerCore-WOW64 [should be installed by default]
Prior to the running the installer, you need to determine how many service accounts you need, and create them if necessary. Recommended practice is to use a separate service account for each service, but you can certainly get away with running all the services under the same account. I'm going use separate accounts for each service, which means I need two accounts. One for the SQL engine, and one for the SQL agent. Create the accounts you need in Active Directory.


With all the prerequisites out of the way, the SQL install is pretty straight forward. You can actually run the setup program from the CD – no need to do a command line install. There is one issue to be aware of - all of the dialog boxes where you would normally browse to select a folder... they don't work. Instead you'll have to just manually type the path you want to use.


After the installation is finished, the last step is to add a new rule to the firewall to allow communication to the SQL engine.  Easiest way to do this, and validate that it was done correctly, is to use Server Manager or MMC with the Windows Firewall snap-in from another machine.
  • Connect to your SQL server
  • Expand Windows Firewall with Advanced Security (under Configuration in Server Manager)
  • Select Inbound Rules
  • Click New Rule...
  • Select Port, Next
  • Enter Specific local ports: 1433, Next
  • Next
  • Uncheck Private and Public (but really it's up to you), Next
  • Enter Name: SQL Server Engine, Finish
With that finished, you can now use Microsoft SQL Server Management Studio from another computer to connect to your SQL server running on Server Core!


Additional port information can be found in this Technet article. I'm not going to run through and test all the different installation options on Server Core, but should you deviate from defaults, that article should help you identify what other ports need to be opened.

Tuesday, June 7, 2011

DC with Copied VM in Hyper-V, Fail

I previously built a domain controller using a copy of a Windows Server 2008 R2 Core template that I had already setup. Everything worked fine until I built another server using the same template and joined it to the domain. Then I ran into this:

Event 5516 The computer or domain [machine] trusts domain [domain]. (This may be an indirect trust.) However, [machine] and [domain] have the same machine security identifier (SID). NT should be re-installed on either [machine] or [domain].

Bottom line here is that you do not ever want to copy/clone/duplicate a server to build a new domain. Your domain controller for your new domain needs to be a unique build, so that it has a unique machine SID. Based on this article, you can feel free to copy/clone/duplicate servers for all other scenarios. It is my understanding that you can even do this when adding a new domain controller to an existing domain - because the new DC will inherit the SID from the domain. This issue only occurs with duplicating a server to build a DC for a new domain.

Now I have to go fix a couple VMs...

Monday, May 30, 2011

Create Windows Server 2008 R2 Core Domain Controller

This is a great article on how to do this from scratch: How to install a Server Core R2 Domain Controller. I did it a little differently.

For me, I'm starting with a copy of my Server Core template which has already been activated and updated. (See previous posting about Copying VMs in Hyper-V.) UPDATE (6/7/2011): If you're building a new domain, use a unique build for your DC, do NOT use a template or copy an existing VM. Avoid my failure. I'm using a fresh Windows Server 2008 R2 Core build setup for remote management. And I'm going to use Core Configurator (http://coreconfig.codeplex.com/) to make things a little easier.

1. Launch Core Configurator: D:\>start_coreconfig.wsf
2. Click "Computer Settings", then "Add or Remove Roles…"
3. Select NetFx3-ServerCore, then click Apply
4. Select all of the following, then click Apply.
  • ActiveDirectory-PowerShell
  • DirectoryServices-DomainController-ServerFoundation
  • DNS-Server-Core-Role
5. If you don't already have these installed, select both, then click Apply.
  • BestPractices-PSH-Cmdlets
  • ServerManager-PSH-Cmdlets

[You have to do NetFx3 first. Don't try to select them all at one time.]

Unfortunately Core Configurator lacks the functionality to create a new domain, so it has to be done manually. If you're adding a DC to an existing domain, it should do the job, but I haven't tried it yet. To create a new domain we need to create an answer file that can be used by dcpromo.

6. Launch notepad on Core and copy or type the following into it:
[DCINSTALL]
InstallDNS=yes
NewDomain=forest
NewDomainDNSName=yourdomain.com
DomainNetBiosName=yourdomain
SiteName=HQ
ReplicaOrNewDomain=domain
ForestLevel=3
DomainLevel=3
DatabasePath="C:\Windows\NTDS"
LogPath="C:\Windows\NTDS"
SYSVOLPath="C:\Windows\SYSVOL"
RebootOnCompletion=yes
SafeModeAdminPassword=Password1
More information on creating the answer file can be found at KB 947034. Edit the line for NewDomainDNSName to specific your domain name, and DomainNetBiosName to the first part of your fully qualified DNS name. Other things that you may need to edit are SiteName, ForestLevel, DomainLevel, and SafeModeAdminPassword. The site name is used for AD Sites and Services. If you leave it out, the default is "Default-First-Site-Name". The levels in my answer file are all set for Windows Server 2008.

[The InstallDNS line didn't do anything for me on Core. So I redid the whole thing, installing the DNS role before running dcpromo. It worked perfectly that way.]

7. Save this file to the root of your C:\ drive and call it dcpromo.txt. Then run the following command:
dcpromo /unattend:C:\dcpromo.txt
After the reboot, your Server Core is now a DC. The domain administrator account inherited the password from the local administrator account. The first thing here that pissed me off is that it made me change the domain administrator account. Security, blah blah blah. And you can't change it back because it violates the password history requirements that Microsoft has setup by default. Screw that.


8. Run the following commands, in order, to reset the domain administrator account using PowerShell.
powershell
import-module ActiveDirectory
Set-ADuser administrator –PasswordNeverExpires $true
Set-ADAccountPassword administrator –Reset
[enter password]
[enter password again]
exit
Biggest reason for me to do this is because I still need to connect to my DC from a machine outside the domain (on my workgroup) now and then. If the domain administrator password matches the administrator password on the workgroup machine, it still allows me to connect via Server Manager and MMC.

Friday, May 27, 2011

Windows Server 2008 R2 Core Workgroup Remote Management

In a previous post I spent a lot of time working out remote management of Windows Server 2008 R2 Core when used in a workgroup instead of a domain. I had gotten MMC working fine for everything that I needed at the time, but it turns out I missed some things (2 that I’ve found so far).

First, if you had tried to use the Device Manager remotely you would have received a nice "Access is denied" error message. This can be resolved by editing a local policy: Computer Configuration\Administrative Templates\System\Device Installation\Allow remote access to the Plug and Play interface. [You have to reboot for the policy to take effect.]

Instructions for this can be found here. But unlike what the article indicates, you do not get the ability to edit devices. Maybe that works in Server 2008, but in Server 2008 R2 you only get read-only access.

Second, and probably more important, when you try to connect to your Server Core via Server Manager from another machine in the workgroup, you will receive the following error:

Connecting to remote server failed with the following error message: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config. For more information, see the about_Remote_Troubleshooting Help topic.

The solution to the problem is found in this TechNet article. You can follow the instructions in the Note section which says to run the following command on the source computer. (By "source computer" they mean the computer that you’re using to connect to Server Core.)

winrm set winrm/config/client @{TrustedHosts="ServerCoreMachineName"}

I’m sure that works fine, but I did something different. I read somewhere else about that fact that this setting exists in local policy. Run gpedit.msc, and navigate to Computer Configuration\Administrative Templates\Windows Components\Windows Remote Management\WinRM Client\Trusted Hosts. You can enter multiple hosts using this method, or in my case just allow all hosts.

WinRM Trusted Hosts via Local Policy

And just like that, Server Manager works over a workgroup.

Monday, May 16, 2011

Hyper-V Tools for R2

After searching the internet for hours it seemed clear that setting up a dedicated network in Hyper-V was something that should be possible. I’d read dozens of posts on how dedicated networking was a new feature for Hyper-V R2. So why couldn’t I figure out how to do it?

I eventually found my way to this blog post from John Howard. The article clearly explained the new networking option and included a picture of what the screen should look like. So… why didn’t my screen look like that? Well, if you scroll down and read the comment posted by John on March 25, 2010, you’ll find out.

“That checkbox only exists on the v2 UI present in Windows 7 and/or Windows Server 2008 R2. The v1 (Vista) UI operating against R2 is not supported - you should be using the v2 UI instead.”

Seriously? The Windows Vista Hyper-V client has not been updated to support Hyper-V R2. And based on John’s comment, I’m going to guess that it never will be. Why doesn’t the download page for the Vista Hyper-V tools explicitly state that they are NOT compatible with Hyper-V R2? Where’s the bright red print? WTF! If the guy who wrote that KB article was standing in front of me I’d kick him square in the nuts! Thanks Microsoft. Thanks for wasting hours of my life.

Here’s another article that John wrote, specifically on using the Vista client for R2.

For most people this probably isn’t an issue. Just manage Hyper-V from the Windows Server 2008 R2 box. But when your corporate network runs Vista and the first Windows Server 2008 R2 box you deployed is Windows Server 2008 R2 Core running Hyper-V… where exactly does that leave you? Screwed, that’s where it leaves you.