Wednesday, May 20, 2009

Project Server 7882: Unable to start services process.

Early morning panic attack! We did a database move of our Project Server database over the weekend. Rather than use the renameserver command, we figured to take the cautious route and set up an alias from the old SQL Server to the new SQL Server - that worked very nicely.

One of the other changes made was to start up the Search Service - previously we had the Office and SharePoint search services disabled, as they were causing errors with the old SQL Server - long story about that! In doing so, I had to update the SSP config page to start up the services.

Looking in the event log, there were a ton of errors complaining "unable to start service process. SSP: Service: ProjectQueueService", then the same error with the pjevtsvc instead. There was also a .Net Runtime 2.0 Error, EventType clr20R3, microsoft.office.project.server. system.nullreferenceexception.

I stopped & started both the Timer and Queue services, but that didn't help. Tried restarting both a few times. Tried an IISReset. Still didn't help.

My service account was set up to Log On As Service in the Local Security Settings User Rights Assignments, Log On as a service, so that wasn't it either.

What did the trick was to change the account used for both services in the Services control panel. The services were set to use the Farm account - I switched this back to using the SSP account instead, restarted the services, and we're up & running with nice quiet logs.

Sunday, April 19, 2009

The workflow failed to start due to an internal error

Another fun bout with SharePoint! This one was a good challenge. I have a big WSP solution built with the VSEWSS extensions, and I wanted to add in my workflow to the solution. The workflow had been working just fine, so I merged in the project file, created a new feature in the WSP View of the project, added in the reference to the project output, and we're good to go!

Except after I associated the workflow and tried to use it, I got the very helpful error "the workflow failed to start due to an internal error". Nothing in the Application viewer, in the 12 Hive logs, just the friendly ol error message and nothing more.

We'll leave off all the things I tried (removing old copies of the workflow, new GUIDs, new namespace, new...well, you get the idea) and here's what did work: when I associated the workflow, I created a new Task and Workflow History list rather than use the ones that were in the site definition. Did that and the workflow is flowing along now. Nice!

I do indeed have a copy of the Tasks list in the site definition.

Friday, March 13, 2009

Convergence 2009

Just came back from working at the HP Booth at Convergence 2009, the Microsoft Dynamics show at New Orleans this year. I learned a lot about Dynamics, and in particular about CRM. (note, I work for EDS, an HP Company). We were the Platinum sponsor of the event & had a nice big booth in the Partner Expo area. I was there discussing SharePoint & how it can work together with CRM. I'll be blogging a bit about that over the next few weeks. Seems to be good stuff!

Friday, February 27, 2009

Magic Jack

One last post for today. Now I normally want this blog to stay focused on SharePoint & related products - like I said this is my virtual memory site, helps me remember stuff I worked on way back when - but I wanted to post about the Magic Jack.

I've gone wireless at home for some time now, but working from home I didn't want to chew up my cell minutes on those long conference calls. I had been using Skype, and was generally pleased with the quality and price, but I did have a few bad/dropped connections and on one conf call folks said I sounded like I was talking in a fish bowl 800 miles away. Not good, especially since the call was with some MS Professional Services folks!

Over the weekend the wife and I saw an ad on TV for The Magic Jack, only $19.95!! Ever skeptical, I decided to try it out anyway. I bought the kit at the local Radio Shack for $40, which includes the first year's service, and I gotta say, I'm impressed. The call quality is quite clear, it's easier to use than Skype - none of that 001 - 866 - 1235466 * thing to dial out, just pick up the phone and dial the number. I used it quite a bit yesterday and my audio was fine, and no one mentioned that fish bowl. Looks like a nice product.

If it turns out to be otherwise I'll let you know! There's a deal with it too, $60 for 5 years of service, instead of $20 per year (OK, $59.95 and $19.95 respectively but a spade is....)

DR with SQL backups and PSCONFIG

As promised...for my client we are only taking SQL Server backups of the databases used by SharePoint, although we may soon be taking disk image backups as well, but that's another story!

Anyway, I had to put together a DR plan documenting the steps to recover SharePoint. There's a great step by step on how to do this for Project Server, posted here. Great article, but there was one thing I didn't understand after reading this...what about my IIS sites? I wasn't sure if I had to create the IIS sites needed for my web apps before I ran that PSCONFIG CONFIGDB command.

Turns out is is NOT necessary. The first thing that command does is to unprovision the provisioned Central Admin site. We follow a standard deployment guide & use the same ports for Central Admin & the Shared Services Provider on all our installs, so even though I did use that same port in the steps prior to the PSCONFIG CONFIGDB step, not necessary. After unprovisioning the site, the process then creates the web apps, the IIS sites, links in the content databases, even for the SSP. Worked just fine.

Now, one last question...what about SSL? Does that also get configured after the IIS site is created? I doubt it, and I need to ensure the existing server's private key is saved off somewhere so we can reinstall it. I need to do one more test on the DR process, I'll let you know what happens.

SSL Exporting to a .PFX....not!

Yesterday I had a bit of a panic. I had a change request scheduled to publish my Project Server site over to our ISA Server. Normally to do this, one goes into the Certificates MMC snap in, locates the Personal certificate assigned to the server, and export it as a .PFX file.

Well, the option to export to .PFX was grayed out, with a warning about "the associated private key cannot be found" - but on the screen where I view the SSL certificate, I was told that there is a private key associated with this certificate. Since ISA Server requires the private key, this was going to be a show stopper!

Digging around I did find one helpful post on the Verisign site: http://forums.iis.net/p/1154109/1888980.aspx

This post lead to me look in the directory C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys, and then further, I was able to locate the file (named with a GUID) in the MachineKeys folder with the time stamp of when we installed the SSL certificate. Turns out even though I'm domain admin, that folder was not owned by the local Administrators group, and that further that GUID named key was owned only by the guy who installed the cert and SYSTEM - Domain Admins & the box admins didn't have permissions to the file or to that folder.

So once I took care of that, changing the ownership on that GUID file & then granting Full Control rights to that key, I was then able to export the certificate as a .PFX file...big relief!!!

Monday, February 09, 2009

The Startup Disk Could Not Be Written To

Well that says it all, ey? I've been working on some DR testing (stay tuned, post coming!!) to recover a Project Server deployment. I created my original VPC, up to the point where the DR stuff kicks in - installation of SharePoint, mainly; so the VPC has the host OS, joined the AD domain, has SQL Server installed.

I copied the VPC at this point then finished up installing the rest of the components - SharePoint, Project Server, service packs, December update, etc.

When I then went to boot up the DR VPC, I was told very nicely that "the startup disk could not be written to"! The VHD file had only the Archive bit set, was not set to be read-only.

I am using Vista w/o SP1 as my host OS. I went into the security settings for the file & saw that the file has entries for System, Administrators & Users. I thought my ID is a member of Administrators, but Users was marked for read-only rights, no Modify or Write rights. So I added in Modify and Write, must have got it right (heh) because then the VHD worked just fine.

Monday, January 12, 2009

Search related errors...

Happy 2009! The new year kicked off with an interesting challenge. I went to the SharePoint environment I've been supporting to do a search, and oddly, there was no Search box on the home page. No biggie, went to Search Center, and when I put in my search terms I got the beloved "Unknown Error" back. Yikes!

Same thing if I tried opening up any of the Search config pages in SSP Config. The logs had all sorts of stuff - missing "S2LeftNav_Administration", missing features, and the SSP Search DB had some broken stored procedures, Proc_MSS_Recompile was missing, proc_MSS_FlushTemp0 was missing some arguements, the Microsoft.SharePoint.Portal.WebControls.SearchBoxEx control was missing, all kinds of stuff.

Found a post where someone had applied the July Infrastructure Update and had seen some of these errors, and had a fix of updating certain specific files. But, I didn't apply that fix as one of my colleagues said there was an issue with the Alternate Access Mapping (subsequently fixed in the October update).

Well, looking back into the Event Viewer, I saw that 'someone did something' - Dec 19th a security patch for SharePoint was applied to close up a hole with Search. With the odd state of things, I guessed that whomever applied the update probably freaked out when the SharePoint Configuration Wizard started & bailed out - bad idea!! Sure enough, re-running the Wizard (with screen caps which are going into an admin guide thank you very much indeed) cleared up the problems.

Next weirdness was a 10 hour incremental crawl, and the indexer showing "Computing ranking" for a long spell. Well, stopped the crawl, reset the index, then did a full crawl, then about 10 minutes later (not a heap of content) started getting search results again.

But of course, Search not working for three weeks ought to have raised at least one trouble ticket!! Looks like it's time for some end user education...

Wednesday, October 22, 2008

FIPS and SharePoint

Hello SharePointers,


Well, one monkey off the back at long last. Way back in February 2008 we ran into a problem where SharePoint would not work when the "System Crptography: Use FIPS compliant algorithms for encryption, hashing and signing" AD security policy was set to Enabled. SharePoint would come up with "An unexpected error occurred", with some 6482 messages in the Event Logs.


Well, along came the August Cumulative Updates, which apparently resolved the issue. So I installed the CU's per the instructions here, enabled FIPS, and what do you know, it still didn't work, still got "An unexpected error occurred"!!!


Well, turns out that the fix for SharePoint indeed just disables logging of those 6482 error codes. In addition to those CU's, you still need to update the web.config for the SharePoint site, so that it tells .Net to use the encryption that FIPS requires. Which oddly enough doesn't seem to be as strong as the non-FIPS encryption, but then I'm no expert! Please see this article for the details on how to modify the machinekey setting to use the 3DES algorith. Good luck!!

Tuesday, August 19, 2008

Custom ServerURL field in a SharePoint list

Well here's an odd one. I'm using a SharePoint list to store configuration info for a web service I'm using. I wanted the URL of the server to be a parameter, so I could switch between dev, model office & production easily. So logically enough I created a custom field called ServerURL to point to the server hosting the web service. Got some weird results:

Trying to use an SPWeb object that has been closed or disposed and is no longer valid.

Turns out ServerURL is an internal field - when I reference ServerURL0 then I get my own field. Reckon it's time to re-create that field with a different name!

Tuesday, April 29, 2008

Day of Dot Net - West Michigan

Woo hoo, accepted as a speaker at the 2008 Day of Dot Net - West Michigan! Good sessions...I want to see the one about using the Wii remote, that'll be very cool. I'll be talking about the fun I've been having with SharePoint, VS 2008, and Workflows. Good stuff!

WM Day of .Net May 10, 2008 - I'll be there!

Showing icons in a list view

My customer threw me a challenge: instead of your typical Red/Amber/Green SLA indicator, they also want to show Blue if they've exceeded the SLA target. And no yellow - either you're red, missed; green, on; or blue, exceeded. So much for using the KPI interface!

I started looking in to the KPI Dashboard sample to see if I could somehow customize this, but after an hour and a cup of Starbuck's it was still none too clear. Then the coffee cleared away the neurons & I recalled seeing discussion on using the spankin' new XSLT Data Viewer. I had done used this with SPS 2003 and it was pretty good then...and it's better now with MOSS.

The XSLT Data Viewer is a MOSS goodie, sorry WSS'ers. To use it, I fired up SharePoint Designer, opened up my list, changed to the 'Split' view, then with a right click I chose the Convert to XSLT Data View option.

In my list I have a SLA column, a Target column, and a Status column. I wanted the indicator in the Status column.

After a few seconds (slow on the VPC for some reason) I was able to view the list. Looking at the XSLT, for the Status column, I changed what was there to:

<!--Status-->
<TD Class="{$IDAVVHVE}"><xsl:choose>
<xsl:when test="@SLA amp-gt; @Target">
<img src="/_layouts/images/KPIDefault-Blue.gif" alt="Exceeded SLA Target"/>
</xsl:when>
<xsl:when test="@SLA= @Target">
<img src="/_layouts/images/KPIDefault-2.gif" alt="Met SLA"/>
</xsl:when>
<xsl:when test="@SLA amp-lt; @Target">
<img src="/_layouts/images/KPIDefault-0.gif" alt="Missed SLA"/>
</xsl:when>
</xsl:choose>
</TD>

Change the 'amp-gt;' to an ampersandgt; (I should find a better blog page) and 'amp-lt;' to the ampersandlt; and you're good to go!

Now I need to come up with a nice shape for the Blue icon. Blue Moon? mmmm, tasty, good idea!

Tuesday, April 08, 2008

EditControlBlock registrationtypes

Been working on adding a new item to the SharePoint drop down menus...easy to do by creating a feature, then defining the custom action one wants to add in. But...I only want my new menu item available for documents. One of my testers saw that the new menu item was on the folder drop down list also, and what do you know, test case failed!

So...I could either add code that points out to the dear user that one ought not use this with folders, or see if there was a way to hide the new menu item from folders - that is, the new menu item appears only for documents.

RegistrationType and RegistrationID to the rescue. These items are part of the custom action XML defined by the Feature.XML. Here's what I've been able to determine. RegistrationType allows for "ContentType" and "FileType" values, as well as List and ProgID.

Specifying "ContentType" is what I'm after. This will allow me to associate the new action with a specific content type. The RegistrationID element is used to specify the ID of the content type. To get this ID, open your document library settings, in the ContentType section click on the content type of interest, then grab the hex string in the URL after the "ctype=" parameter. Paste this value in to the RegistrationID value.

Alternatively, if I only want this feature to appear for ".xls" files, then I'd use the FileType RegistationType, and the value "xls" for the registationID. Repeat the CustomAction block for other file types.

The completed Feature.XML looks like:

Feature.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="1a8e5fae-577f-4cbd-addb-a31d875300a6"
Title="Copy Document"
Description="My custom document copy feature"
Version="1.0.0.0"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="CopyDocument.xml" />
</ElementManifests>
</Feature>


CopyDocument.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Per Item Dropdown (ECB)-->
<CustomAction
Id="Custom.PromoteDocument"
RegistrationType="ContentType"
RegistrationId="ctype=0x01010038C4691B76BEB34CB53E65CADDF82AD3"
Location="EditControlBlock"
Rights="EditListItems"
Title="Promote Document">
<UrlAction Url="~site/_layouts/MyCustomCopyDocument.aspx?List={ListId}&ID={ItemId}"/>
</CustomAction>
</Elements>

Note also the use of {ListId} and {ItemID} - I use this with my code behind file to get the SPList and SPListItem from the content SPWeb. With these I can then get to the attachment object of the SPListItem, so I can copy that document around.

Monday, January 28, 2008

Change AD password with code

New cool stuff with the .Net 3.5 Framework! The System.DirectoryServices.AccountManagement namespace adds all kinds of clean methods for working with user objects. I wanted to put together a simple web part to change AD passwords - found a few good examples out there, but since I have the 3.5 Framework installed on my server I of course had to give the latest a whirl.

First, for background, take a look at this excellent article: AccountManagement Namespace

Joe & Ethan give a great overview of using the new features in 3.5.

In my web part, then, I have a three text boxes and a "Change" button - the bulk of the web part is based on the work done by Robin Meuré did here, with a few cosmetic changes but the main change done to the code in the btn_Click, posted here:

SPWeb webContext = SPControl.GetContextWeb(Context);
string strLoginName = webContext.CurrentUser.LoginName;
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain);

UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, strLoginName);

try
{
string oldPasswordValue = oldpassword.Text;
string newPasswordValue = newpassword.Text;
user.ChangePassword(oldPasswordValue, newPasswordValue);
output.Text = "Password changed. Please close this browser window and log back on with your new password.";
}
catch (Exception ex)
{
output.Text += String.Format("Password couldn't be changed due to restrictions: {0}", ex.Message);

}
finally
{
user.Dispose();
domainContext.Dispose();
}


That's it! Easy-peasy.

Note that when I was getting the user object, I first tried just getting a UserPrincipal object by context:

UserPrincipal user = new UserPrincipal(domainContext);

Problem here was, when i invoked the ChangePassword method, I got the very friendly message back that: ChangePassword method can not be called on an unpersisted Principal object. So instead I get the user account info from the SPContext object, pass that in to the Directory services methods, and we're good to go.

Thursday, January 17, 2008

To copy a file from one lib to another...

Finally back to SharePoint coding after a long hiatus of process documentation - feels great!

So the challenge has been adding a menu item to the Edit Control Block to copy items from one library to another. There are some good examples out there, Andrew Connell's blog has a great example, so I won't repeat it - but I will show all of the code I have. Might help you out.

In this scenario, I have three "document center" on my portal: Working, Staging, and Production ("Docs"). I want to let my users choose when to promote a document, but I didn't want this to be workflow dependent: ie, not based on setting a document property, or based on a check in event, etc - let the user decide.

The drop down for the Edit Control Block was the answer. "Promote Document" will now appear for users with the right access. This feature passes the list ID and item ID to an ASPX page, which then does the work of copying the document over.

Some helpful hints:

In your feature's XML file, you can assign the "Rights" value to the minimum rights a user needs in order to see your new ECB item. That way it won't show up for everyone - nice!

Inside the code, after moving the file, the field items won't copy also. So you'll need to copy them with code. But then when you perform a ListItem.Update the ModifiedBy turns in to the System Account (assuming impersonation) - ick! So intead use the ever cool SPListItem.SystemUpdate() which does *not* change the Modified or ModifiedBy properties.

Here we are:

Feature.xml
<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="1a8e5f42-5742-4c42-ad42-a31d87530042" // enter your own guid
Title="Promote Document"
Description="Promote a document from work to staging, or from staging to production" Version="1.0.0.0"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="PromoteDocument.xml" /> <
/ElementManifests>
</Feature>

The CopytoWorking.XML file, the manifest listed in feature.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Per Item Dropdown (ECB)-->
<CustomAction
Id="CopytoWorking"
RegistrationType="ContentType"
RegistrationId="0x01"
Location="EditControlBlock"
Rights="EditListItems"
Title="Promote Document">
<UrlAction Url="~site/_layouts/PromoteDocument.aspx?List={ListId}&ID={ItemId}"/> </CustomAction>
</Elements>

and finally the ASPX page. This uses the Application.Master page layout so the new page looks sort of like a SharePoint page. Worked for me!
<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page language="C#" MasterPageFile="~/_layouts/application.master" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="~/_controltemplates/ButtonSection.ascx" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="System.Net" %>
<asp:Content ID="Content1" ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
<asp:Literal id="PageTitleLabel1" runat="server" ></asp:Literal>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
<asp:Label id="PageTitleLabel" runat="server" ></asp:Label>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderId="PlaceHolderAdditionalPageHead" runat="server">
<script runat="server">
public void CancelClick(Object obj, EventArgs e)
{
SPWeb oWeb = SPContext.Current.Web;
SPList oList = oWeb.Lists[new Guid(Context.Request["List"])];
Response.Redirect(oList.DefaultViewUrl);
}

public void ConfirmClick(Object obj, EventArgs e)
{
SPWeb oWeb = SPContext.Current.Web;
SPUser currentUser = oWeb.CurrentUser;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
oWeb.AllowUnsafeUpdates = true;
SPList oList = oWeb.Lists[new Guid(Context.Request["List"])];
SPListItem oItem = oList.GetItemById(int.Parse(Context.Request["ID"]));
byte[] fileContent = oItem.File.OpenBinary();
String currentUrl = oItem.File.ServerRelativeUrl;
string[] currentUrlSplit = currentUrl.Split('/');
if (currentUrlSplit[1].CompareTo("Staging") == 0)
{ currentUrlSplit[1] = "Docs"; }
if (currentUrlSplit[1].CompareTo("Working") == 0)
{ currentUrlSplit[1] = "Staging"; }

StringBuilder sb = new StringBuilder();
String siteUrl = oWeb.ParentWeb.Url;
string[] siteUrlSplit = siteUrl.Split('/');
sb.Append(siteUrlSplit[0]); // this is the protocol
sb.Append("//");
sb.Append(siteUrlSplit[2]); // this is the server
string server = sb.ToString();
// now add the rest of the URL - site & folders
for (int i = 1; i < currentUrlSplit.Length; i++)
{
sb.Append("/");
sb.Append(currentUrlSplit[i]);
}
String newUrl = sb.ToString();
SPWeb newWeb = new SPSite(newUrl).OpenWeb();
newWeb.AllowUnsafeUpdates = true;
EnsureParentFolder(newWeb, newUrl);

// copy the file over
SPFile newFile = newWeb.Files.Add(newUrl, fileContent, currentUser, currentUser, oItem.File.TimeCreated, oItem.File.TimeLastModified);

// add the properties
foreach (SPField oField in oItem.Fields)
{
if (!oField.ReadOnlyField &&
oField.InternalName != "ContentType" &&
oField.Type != SPFieldType.Invalid &&
oField.Type != SPFieldType.WorkflowStatus &&
oField.Type != SPFieldType.File &&
oField.Type != SPFieldType.Computed &&
oField.Type != SPFieldType.User &&
oField.Type != SPFieldType.Lookup)
{
newFile.Item[oField.Title] = oItem[oField.Title];
}
}

newFile.Item.SystemUpdate(true);
newWeb.Dispose();
Response.Redirect(oList.DefaultViewUrl);
});
}

public static string EnsureParentFolder(SPWeb parentSite, string destinUrl)
{
destinUrl = parentSite.GetFile(destinUrl).Url;
int index = destinUrl.LastIndexOf("/");
string parentFolderUrl = string.Empty;
if (index > -1)
{
parentFolderUrl = destinUrl.Substring(0, index);
SPFolder parentFolder = parentSite.GetFolder(parentFolderUrl);
if (!parentFolder.Exists)
{
SPFolder currentFolder = parentSite.RootFolder;
foreach (string folder in parentFolderUrl.Split('/'))
{
currentFolder = currentFolder.SubFolders.Add(folder);
}
}
}

return parentFolderUrl;
}
</script>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderId="PlaceHolderPageImage" runat="server"> <img src="/_layouts/images/blank.gif" width=1 height=1 alt="" /></asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderId="PlaceHolderPageDescription" runat="server">
<asp:Label id="PageDescriptionLabel" runat="server" ></asp:Label>
<asp:PlaceHolder id="PublishDescription" Visible="true" runat="server">
<tr> <TD height="8px"><img src="/_layouts/images/blank.gif" width="1" height="8" alt=""></td></tr>
<tr><td colspan="2">
<table cellpadding="2" cellspacing="1" width="100%" class="ms-informationbar" style="margin-bottom: 5px;" border=0>
<tr><td width="10" valign="middle" style="padding: 4px">
<img id="Img1" src="/_layouts/images/exclaim.gif" alt="<%$Resources:wss,exclaim_icon%>" runat="server"/></td>
<td class="ms-descriptiontext"><SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" text="Click Confirm to continue or Cancel to return. Promoting a document will remove it from this library and move it from Work to Staging, or Staging to Production." EncodeMethod='HtmlEncode'/>
</td></tr></table>
</td></tr>
</asp:PlaceHolder></asp:Content>
<asp:Content ID="Content6" ContentPlaceholderID="PlaceHolderMain" runat="server">
<asp:Button CssClass="ms-ButtonHeightWidth" AccessKey="o" ID="Confirm" runat="server" Text="Confirm" OnClick="ConfirmClick"/>
<asp:Button CssClass="ms-ButtonHeightWidth" AccessKey="a" ID="Cancel" runat="server" Text = "Cancel" OnClick = "CancelClick" />
</asp:Content>

Thursday, September 20, 2007

Secured section inside web.config

Finally a chance to work on some code - six months of writing documentation just isn't good for the health!!

So in my web app I want to store an ID and password, and I want to encrypt these keys to secure them. But there are other settings I want to leave in clear text to make for easier editing for my admins, and less web page work for me. The answer - custom sections inside my ASP.Net 2.0 web.config.

Good stuff! But as usual, it ain't straight forward. Here's what I ended up implementing. The web.config contains the following entry to create the new secured section:

















Then inside my project I created the following class. It sets up the key/value pairs I needed inside my secure settings section of web.config.

Then, to use the class, easy peasy - just need to iterate through the pairs to find the key I need:

private string GetSecureConfigKey(string key)
{
String retval = "";
SecureConfiguration configInfo = (SecureConfiguration)ConfigurationManager.GetSection("secureSettings");
if (configInfo != null)
{
foreach (SecureConfigurationADKey secureKey in SecureConfiguration.GetConfig().ADKeys)
{
if (secureKey.Key.ToLower().Equals(key.ToLower()))
retval = secureKey.Value;
}
}
return retval;
}

To set the value, I needed to do something similar - but kept getting an error message that the configuration was read only. Huh?? Problem was I tried setting the value inside my foreach loop - tsk tsk. So instead I use the following:

Configuration objConfig = WebConfigurationManager.OpenWebConfiguration("~");
SecureConfiguration configInfo = objConfig.GetSection("secureSettings") as SecureConfiguration;
foreach (SecureConfigurationADKey secureKey in SecureConfiguration.GetConfig().ADKeys)
{
if (secureKey.Key.ToLower().Equals(Key.ToLower()))
{
index = i;
}
i++;
}

then

configInfo.ADKeys[index].Value = Value;

And all's well that ends well. Note too that I check the .IsProtected property of my section when I save the value:

if (!configInfo.SectionInformation.IsProtected)
{
configInfo.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
configInfo.SectionInformation.ForceSave = true;
}

and it works!

The class I use for the new web.config section follows:
public class SecureConfiguration : ConfigurationSection
{
public static SecureConfiguration GetConfig()
{
return ConfigurationManager.GetSection("secureSettings") as SecureConfiguration;
}

[ConfigurationProperty("adInfo")]
public SecureConfigurationADKeysCollection ADKeys
{
get
{
return this["adInfo"] as SecureConfigurationADKeysCollection;
}
set
{
this["adInfo"] = value;
}
}
}

public class SecureConfigurationADKey : ConfigurationElement
{
[ConfigurationProperty("key", IsRequired = true)]
public string Key
{
get
{
return this["key"] as string;
}
set
{
this["key"] = value;
}
}

[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get
{
return this["value"] as string;
}

set
{
this["value"] = value;
}
}
}

public class SecureConfigurationADKeysCollection : ConfigurationElementCollection
{
public SecureConfigurationADKey this[int index]
{
get
{
return base.BaseGet(index) as SecureConfigurationADKey;
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}

protected override ConfigurationElement CreateNewElement()
{
return new SecureConfigurationADKey();
}

protected override object GetElementKey(ConfigurationElement element)
{
return ((SecureConfigurationADKey)element).Key;
}
}

Friday, August 17, 2007

ASP.Net 2.0 and web part properties

So I take it back - one can store web part properties with web parts. I was missing the WebBrowsable property - that seems to do the trick. From Ted Pattison's book, Inside WSS v3 - good reading indeed!

private string xmlUrl;
[ Personalizable(PersonalizationScope.Shared),
WebBrowsable(true),
WebDisplayName("Feed Url"),
WebDescription("Set your RSS feed's XML URL here!")
]
public string XmlUrl {
get { return xmlUrl; }
set { xmlUrl = value; }
}

Tuesday, May 08, 2007

Web Parts catalog

Had a question come up last week - where's a good place for finding a catalog of SharePoint web parts? Well, here's a list - some pretty decent stuff. I like the Charting web part, nice way to add charts to WSS v2. Note that these are all for V2/2003.

http://www.sharepointcustomization.com/resources/wpsamples.htm

Wednesday, May 02, 2007

Outlook 2007 - Deliver Mail to Offline Folder

OK, it's not a post about SharePoint (got one of those coming - workflow - cool stuff) but I was looking & looking for this when it was right in front of me all the time. I like having my Outlook mail pulled off the server & delivered locally. Cached Exchange Mode, yes? In Outlook 2007 to get mail delivered to the Offline folder and downloaded from the server, so that I don't get the dreaded "Mailbox is getting pretty big there dude" message, do the following:

1) Create an offline mail folder
2) Tools / Options / Mail Setup / Data Files
3) Locate your offline folder, click once, then click "Set as Default"
4) Wait whilst your mail downloads....
5) Send you and all your colleages a 25MB attachment!!

Tuesday, April 10, 2007

Day of Dot Net 2007 Speaker

I'll be speaking at Day of Dot Net 2007 in Ann Arbor 5/5 - see you there!! I'll be talking about SharePoint 2007 - WSS & MOSS & all that good stuff!



Day of .Net May 5, 2007 - I'll be there!