Second Life of a Hungarian SharePoint Geek

October 16, 2017

Editing the PWS Site Address does not work if the URL is very long

Filed under: Bugs, PowerShell, Project Server — Tags: , , — Peter Holpar @ 19:52

As part of our daily jobs, we should rename projects on our Project Server occasionally. For this kind of change, we have already a “human workflow” or a check list: tasks, we should perform one after another.

The Standard Process

These steps include:

1. Changing the project name on the Project Details page:

image

2. Setting the project web site (PWS) title and URL on the Title, Description, and Logo page of Site Settings:

image

(Note, there is a bug already on this page. If the URL is long enough, it is displayed duplicated in the path under URL name, once in the fix part, and once in the text box. It is already part of the example URL bottom on the left as well. See the screenshot above.)

3. Re-binding the project to the relocated PWS via the PWA Settings / Connected SharePoint Sites / Edit Address.
(For more information, see The Edit Site Address settings on this page)

image

image

image

4. Beyond the steps described above we have some extra steps, like renaming project groups, setting further PWS properties, and so on, but these steps are all custom to our current solution.

The Problem

A few month ago one of the Project Server administrators complained that he is not able to change the site address of  PWS. He was to change the URL from (let’s say) VeryVeryLongProjectSiteUrl to VeryVeryLongProjectSiteUrlNew. Although he has not got any error message, and I’ve not found any related entry in the ULS logs either, the original URL of the PWS remained unchanged.

Changing the Site Address via PSI and PowerShell

First, I wrote a PowerShell script that uses the PSI to change the PWS binding via the UpdateProjectWorkspaceAddress method.

  1. $pwsCurrentUrl = "http://YourProjectServer/PWA/VeryVeryLongProjectSiteUrl"
  2. $projUrl = "PWA/VeryVeryLongProjectSiteUrlNew" # that is the destination URL of the PWS, it should be the server relative URL, including PWA in the path!
  3.  
  4. $web = Get-SPWeb $pwsCurrentUrl
  5.  
  6. # if you already know the IDs (project ID and site ID of the PWA site)
  7. # $projId = [Guid]"99894c16-7a03-e411-83c6-005056b45654"
  8. # $siteId = [Guid]"e1b9fba5-09ad-441a-8679-6286dde059ab"
  9.  
  10. # or get the IDs from the PWS properties
  11. $projId = $web.AllProperties["MSPWAPROJUID"]
  12. $siteId = $web.Site.Id
  13.  
  14. # figure out the PWA url dinamically
  15. # $pwaUrl = $web.AllProperties["PWAURL"] # or
  16. $pwaUrl = $web.Site.Url
  17.  
  18. # we are using the Project PSI service
  19. $svcPath = "/_vti_bin/psi/Project.asmx?wsdl"
  20.  
  21. # https://social.technet.microsoft.com/Forums/scriptcenter/en-US/9d0d73bb-b2bf-4528-beea-321cf82a9b89/problem-executing-a-script-what-uses-namespace-parameter-in-the-newwebserviceproxy-cmdlet
  22. If ($global:svcPSProxy -eq $null)
  23. {
  24.   Write-Host "Connecting PSI proxy at $pwaUrl …"
  25.   $global:svcPSProxy = New-WebServiceProxy -Namespace PSIProxy -Uri ($pwaUrl + $svcPath) -UseDefaultCredential
  26. }
  27. Else
  28. {
  29.   Write-Host "Reusing existing PSI proxy"
  30. }
  31.  
  32. # change the project – PWS binding, or create a new PWS if there is no PWS at the destination
  33. $svcPSProxy.UpdateProjectWorkspaceAddress($projId, $projUrl, $siteId)

Note, that based on my tests, the script not only maps an existing PWS to the project, but it creates a new PWS if there is no PWS at the destination URL specified.

Finding the Bug on the Web Page

After completing the task via the script above, I decided to find out the reason, the UI does not work in this case.

As far as I see, it is a simple silly error in the JavaScript on the Edit Site Address page (\TEMPLATE\LAYOUTS\PWA\ADMIN\EditSiteAddressDlg.aspx).

It is the Init function on that page:

  1. function Init()
  2. {ULSH9J:;
  3.    oArgs = window.frameElement.dialogArgs;
  4.    sProjName = oArgs.sProjName;
  5.    sServerAddr = ((oArgs.sServerAddr != null) ? oArgs.sServerAddr : "");
  6.    sSubwebName = oArgs.sSubwebName;
  7.  
  8.    idProjectNameTD.title = sProjName;
  9.    if(sProjName.length > 40)
  10.    {
  11.       sProjName = sProjName.slice(0,40) + "…";
  12.    }
  13.    XUI.Html.SetText(idProjectNameTD, sProjName);
  14.  
  15.    if((sServerAddr != "") && (sSubwebName != ""))
  16.    {
  17.       var sUrl = sServerAddr + "/" + sSubwebName;
  18.       idServerAddressTD.title = sUrl;
  19.       if(sUrl.length > 40)
  20.       {
  21.          sUrl = sUrl.slice(0,40) + "…";
  22.       }
  23.       XUI.Html.SetText(idServerAddressTD, sUrl);
  24.    }
  25.  
  26.    idSubwebName.value = sSubwebName;
  27.  
  28.    RecalculateTargetURL();
  29.  
  30.    origTargetUrl = XUI.Html.GetText(idTargetURL);
  31. }

This function invokes the RecalculateTargetURL function (see below) to trim the end of the URL of the PWS if it is longer then 50 characters, and to append … to it. This value is displayed then on the page as Destination URL. In the Init function we store the original value in the origTargetUrl variable.

  1. function RecalculateTargetURL()
  2. {ULSH9J:;
  3.    var sURL = idVirtualServerDropdown[idVirtualServerDropdown.selectedIndex].text;
  4.    sURL += "/" + TrimSpaces(idSubwebName.value);
  5.  
  6.    idTargetURL.title = sURL;
  7.  
  8.    if(sURL.length > 50)
  9.    {
  10.       sURL = sURL.slice(0,50) + "…";
  11.    }
  12.  
  13.    XUI.Html.SetText(idTargetURL, sURL);
  14. }

image

The very same RecalculateTargetURL function is invoked on each key press or on changes in the Site URL text box to keep the value of the Destination URL on the page current:

<input DIR="ltr" type="text" id="idSubwebName" name="idSubwebName" style="width: 160px" onchange="RecalculateTargetURL()" onkeyup="RecalculateTargetURL()" …

Note, that the RecalculateTargetURL function is registered for the onchange event of Web Application dropdown either.

The problem is, that the script uses the trimmed values for comparison in the OkBtn_OnClick function (see the method below, including some server side code) to decide, if there is any change in the URL (see the condition with the comment “If nothing changed then we don’t have to do anything” below). Of course, if you have long site (and project) names, and you change something only at the end of the name, this comparison won’t detect the change.

  1. function OkBtn_OnClick()
  2. {ULSH9J:;
  3.    if(idSiteEnabled.checked && (TrimSpaces(idSubwebName.value) == ""))
  4.    {
  5.       XUI.Html.SetText(idAlertBox, PJUnescape("<%=PJEscape(PJUtility.GetLocalizedString(IDS.ADMIN_EDITSITEADDRESSDLG_WEB_NAME_BLANK_ALERT))%>"));
  6.       XUI.Html.SetText(idRequiredFieldIndicator, "*");
  7.       idSubwebName.focus();
  8.       return;
  9.    }
  10.    
  11.    // If nothing changed then we don't have to do anything.
  12.    if((origTargetUrl == XUI.Html.GetText(idTargetURL)) && !idSiteNotEnabled.checked)
  13.    {
  14.       window.frameElement.commonModalDialogClose(0, null);
  15.       return;
  16.    }
  17.    //if we remove the site
  18.    else if(idSiteNotEnabled.checked)
  19.    {
  20.       idSubwebName.value = "";
  21.       oArgs.sNewSubwebName = "";
  22.       oArgs.sNewServerUID = "<%=Guid.Empty%>";
  23.    }
  24.    //we change the site
  25.    else
  26.    {
  27.       oArgs.sNewServerUID = idVirtualServerDropdown[idVirtualServerDropdown.selectedIndex].value;
  28.       var sTemp = TrimSpaces(idSubwebName.value);
  29.  
  30.       // Remove the trailing slash.
  31.       if(sTemp.charAt(sTemp.length – 1) == '/')
  32.       {
  33.          sTemp = sTemp.substr(0, sTemp.length – 1);
  34.       }
  35.       oArgs.sNewSubwebName  = sTemp;
  36.       window.returnValue    = true;
  37.    }
  38.  
  39.    window.frameElement.commonModalDialogClose(1, oArgs);
  40. }

Note however, that if you click on the Test URL button, a new browser tab would be opened with the right destination URL (and not the trimmed one). The right new URL is displayed as a tooltip as well, when you move the mouse pointer over the URL right to the Destination URL title.

function TestUrl_OnClick(event)
{ULSH9J:;
   window.open(idTargetURL.title);
}

As you can see, the TestUrl_OnClick function uses the tooltip of the Destination URL (idTargetURL.title) to open the site. It is important to point out, that the value of  idTargetURL.title is set to the full URL, and not to the trimmed one in the RecalculateTargetURL function (see above).

image

A Quick Workaround via the Web Page

If you don’t want (or not allowed) to use the PowerShell script above to relocate your PWS, there is a simple workaround that uses the standard web admin UI. Start the F12 Developer Tools in Internet Explorer, and set a breakpoint on the line

if((origTargetUrl == XUI.Html.GetText(idTargetURL)) && !idSiteNotEnabled.checked)

on the Edit Site Address page. If the breakpoint get hit, jump over the condition by setting the next statement of execution direct onto the line:

oArgs.sNewServerUID = idVirtualServerDropdown[idVirtualServerDropdown.selectedIndex].value;

The Long-Term (but Dirty) Solution

Although it is not supported, you can change the code in the EditSiteAddressDlg.aspx page as well. I strongly suggest you to take a backup of this file first.

There are two options to fix the error, the first one is to modify the Init function to save the original full (!) URL instead of the trimmed one:

//origTargetUrl = XUI.Html.GetText(idTargetURL);
origTargetUrl = idTargetURL.title;

Then use this value to compare with the current untrimmed URL value in the OkBtn_OnClick function:

// If nothing changed then we don’t have to do anything.
//if((origTargetUrl == XUI.Html.GetText(idTargetURL)) && !idSiteNotEnabled.checked)
if((origTargetUrl == idTargetURL.title) && !idSiteNotEnabled.checked)

The other option is to forget origTargetUrl, and take the original full URL from the tooltip of the Current site address. As you can see on the screenshot after the RecalculateTargetURL function code snippet above, this tooltip contains the untrimmed URL version.

In this case, the new comparison in the OkBtn_OnClick function:

if((idServerAddressTD.title == idTargetURL.title) && !idSiteNotEnabled.checked)

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: