Second Life of a Hungarian SharePoint Geek

April 20, 2016

How to Start the Wrong SharePoint Workflow Unintentionally from the UI

Filed under: Bugs, SP 2010 — Tags: , — Peter Holpar @ 22:38

A few weeks ago we wanted to start a specific workflow on a SharePoint list item, that is located in a list that has multiple custom workflows (none of them has a workflow initiation form) associated with it. We performed the action at the very same time with a colleague of mine (having only 4 seconds difference, as it turned out later), independently from each other. After a while, when we checked the status of the item, we found, that two workflows started on the item. The one, we both wanted to start, was started by my colleague, and I’ve started an other workflow. My colleague said I might have clicked the wrong workflow on the workflow.aspx web page, but I was sure I clicked the right one.

What happened?

I was able to reproduce the issue in our test environment by loading the Workflow page in two separate browser tabs, and starting the same workflow in each of them. I was pretty confident, that the developers of the page made the mistake to try to start the selected workflow by its index in the array of available workflows, instead of the Id of the workflow association.To be sure, I’ve checked the source of the workflow.aspx file (located in the LAYOUTS folder, having a lot of in-line code) and the class behind it, the Microsoft.SharePoint.ApplicationPages.WorkflowPage class (located in the Microsoft.SharePoint.ApplicationPages assembly).

Note: If you try to start the last workflow on the page (or as a special case of it, you have only a single associated workflow) twice in two separate browser, you get an error on the second try. In this case you have the following entry (Level: Unexpected) in the ULS logs:

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.  Parameter name: index    at System.Collections.ArrayList.get_Item(Int32 index)     at Microsoft.SharePoint.ApplicationPages.WorkflowPage.OnLoad(EventArgs e)     at System.Web.UI.Control.LoadRecursive()     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

The OnLoad method of the WorkflowPage class calls the ConstructStartArray method if it is not a page post back. In the ConstructStartArray a new ArrayList is created in the m_alwaStart field, and it is populated by the available workflows, by iterating through the workflow associations on the list, on the content type, and on the web level. In each case, the FCanStartWorkflow method of the base class (WorkflowPageBase) is invoked to ensure the current user has the permission to start the workflow manually (see the SPWorkflowAssociation.PermissionsManual property) and if there is no running instance of this workflow type on the item already (via the FIsRunningWt method of the same class). By the end of the ConstructStartArray method the ArrayList in the m_alwaStart field contains the workflows the user can start on the item. So far so good.

Let’s see how this list is rendered in the in-line code of the workflow.aspx page.

<%
    bool fColumnPosition=true;
    int iwa = 0;
    strImageUrl = "/_layouts/images/availableworkflow.gif";
    foreach (SPWorkflowAssociation wa in m_alwaStart)
    {
        string strIniUrl = GenerateStartWorkflowLink(wa);
        if (strIniUrl == null)
            strIniUrl = "javascript:StartWorkflow(" + Convert.ToString(iwa) + ")";
%>

<%

    iwa++;

%>

As you can see, the parameter used with the StartWorkflow method is really a simple counter, the index of the workflow association in the ArrayList in the m_alwaStart field.

The StartWorkflow JavaScript method simply sets a form value (iwaStart) and posts back the page:

function StartWorkflow(iwa)
    var elIwaStart = document.getElementById("iwaStart");
    elIwaStart.value = iwa;
    theForm.submit();
}

The server side GenerateStartWorkflowLink method of the WorkflowPage class, that you can also see in the inline-code above should display the workflow initiation form for the workflow association, if any exists.

Back to the server side, and let’s see what happens with the value posted back by the StartWorkflow method in the OnLoad method of the WorkflowPage class. If the request is a post back, than it reads the index of the workflow to start, and looks up the workflow by this index from the array of workflow associations in the m_alwaStart field:

int num2 = Convert.ToInt32(base.Request.Form["iwaStart"]);
if (num2 >= 0)
{
    base.StartWorkflow((SPWorkflowAssociation) this.m_alwaStart[num2]);
}

Problem: this array might be not the same, as the one returned on the first page load. If a workflow that precedes the workflow we want to start (or the same workflow) is started in the meantime, the workflow associations are changed (for example, workflows are registered or removed on the web, on the list or on the content type level), or the permissions are changed, it is possible (or even very likely) that the user starts another workflow, not the one he clicked on on the web UI.

Solution: would be to use the Id (of type Guid) of the Microsoft.SharePoint.Workflow.SPWorkflowAssociation instance as the identifier of the item in the array instead of  the index / position in the array.

That would mean in the in-line code, instead of using the iwa counter:

strIniUrl = "javascript:StartWorkflow(" + Convert.ToString(wa.Id) + ")";

and in the OnLoad method, handling the post back as:

Guid waId = Guid.Parse(base.Request.Form["iwaStart"] as string);
base.StartWorkflow(this.m_alwaStart.ToArray().First<SPWorkflowAssociation>(wa => wa.Id == waId));

Note: I could reproduce this buggy behavior in SharePoint 2010 and in SharePoint 2013 using site collections that were not upgraded to the SharePoint 2013 mode. However, as long as I see, “native” SharePoint 2013 sites do suffer from the same kind of problem.

Reusing PSI Proxy Objects from PowerShell

Filed under: PowerShell, PS 2013, PSI — Tags: , , — Peter Holpar @ 22:06

Assume you create a PowerShell script that invokes PSI to perform some actions on Project Server. For example, creating a custom field as described in my former post. You save the script as a .ps1 file and invoke it from the PowerShell shell. Assume it has some parameters and your goal is to invoke it multiple times with various parameter sets. On of the first step in the script is of course the creation in the PSI proxy object, as shown in the original version:

$pwaUrl = "http://YourProjectServer/pwa&quot;
$svcPath = "/_vti_bin/psi/CustomFields.asmx?wsdl"

$svcPSProxy = New-WebServiceProxy -Namespace PSIProxy -Uri ($pwaUrl + $svcPath) -UseDefaultCredential

later in your code you invoke a method on the proxy object:

$svcPSProxy.CreateCustomFields($customFieldDataSet, $false, $true)

On the first run of the script it performs the actions without error, however on the next (and on each later) run it gives you an exception like this:

Cannot convert argument "cfds", with value: "PSIProxy.CustomFieldDataSet", for
"CreateCustomFields" to type "PSIProxy.CustomFieldDataSet": "Cannot convert
the "PSIProxy.CustomFieldDataSet" value of type "PSIProxy.CustomFieldDataSet"
to type "PSIProxy.CustomFieldDataSet"."
At line:1 char:1
+ $svcPSProxy.CreateCustomFields($customFieldDataSet, $false, $true)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

Do you see the strange error message?

Cannot convert the "PSIProxy.CustomFieldDataSet" value of type "PSIProxy.CustomFieldDataSet" to type "PSIProxy.CustomFieldDataSet".

An object having a specific type cannot be converted to a type having the same type name. Very weird. PowerShell seems to cache the object types created dynamically by the New-WebServiceProxy cmdlet on the first run, and these types seem to be not compatible (at least, in the .NET-sense) with the ones created on the next runs. The single (or at least the most simple) solution seems to be to restart the shell after each run, but it is not very nice, to say the least.

Fortunately, I’ve found a better way in this thread for the “recycling” of the proxy object created on the first execution. Note, that the solution I find there is not the accepted answer as I wrote this post. See the answer from existenz7 on February 07, 2013 1:08 PM.

So I’ve changed the proxy creation part in my script to the form:

If ($global:svcPSProxy -eq $null)
{
  Write-Host "Connecting PSI proxy at $pwaUrl …"
  $global:svcPSProxy = New-WebServiceProxy -Namespace PSIProxy -Uri ($pwaUrl + $svcPath) -UseDefaultCredential
}
Else
{
  Write-Host "Reusing existing PSI proxy"
}

You can invoke the proxy method just like earlier:

$svcPSProxy.CreateCustomFields($customFieldDataSet, $false, $true)

Note: I typically omit this kind of proxy creation from my code posted here on the blog just not to disturb you with details that are not relevant to the problem discussed actually in the post. However, I suggest you to apply the same technique to avoid the type incompatibility issue mentioned above.

Handling PSI Errors in PowerShell Scripts

Filed under: PowerShell, PS 2013, PSI — Tags: , , — Peter Holpar @ 22:03

Recently I work pretty much with PSI calls from PowerShell, to automate such administrative tasks, that are not available in the Project Server Client Object Model, like setting the rules of graphical indicators for the custom fields (see full code here). When using such code, sooner or later you receive an PSI exception, either due to lack of data, an invalid data, or simply because the entity you are working with are not checked out to you.

For example, if you call the CreateCustomFields as shown in the former post:

$svcPSProxy.CreateCustomFields($customFieldDataSet, $false, $true)

you may receive a PSI error that is displayed by default so in PowerShell:

Exception calling "CreateCustomFields" with "3" argument(s):
"ProjectServerError(s) LastError=CustomFieldLowerOrderBitsOutOfRange
Instructions: Pass this into PSClientError constructor to access all error
information"
At line:1 char:1
+ $svcPSProxy.CreateCustomFields($customFieldDataSet, $false, $true)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : SoapException

To display the details of the error, I’ve implemented the error handling similar to the one you find in several PSI-related C# article on MSDN (see this one, for example):

Try {
    $svcPSProxy.CreateCustomFields($customFieldDataSet, $false, $true)
}
Catch [System.Web.Services.Protocols.SoapException] {
    $ex = $_.Exception
    $clientError = New-Object Microsoft.Office.Project.Server.Library.PSClientError($ex)   
    $psiErrors = $clientError.GetAllErrors()
    $psiErrors | % {
        $err = $_
        Write-Host ([int]$err.ErrId) $err.ErrName
        For($i = 0; $i -lt $err.ErrorAttributes.Length; $i++) {
            Write-Host $err.ErrorAttributeNames()[$i] $err.ErrorAttributes[$i];
        }
    }
}

In my case, the specific error information is displayed as error code – error name pairs, like:

11521 CustomFieldMaskDoesNotMatchEntityType
11522 CustomFieldLowerOrderBitsOutOfRange

Note 1: I typically omit this kind of error handling from my code posted here on the blog just not to disturb you with details that are not relevant to the problem discussed actually in the post. It may be OK for you to run the code as it is posted as long as there is no error, however if an exception is thrown, it is good to know how to access the details of the problem. Alternatively, you can use Fiddler to capture the server response, and check the raw XML for the error information.

Note 2: A comprehensive list of PSI error codes is available here, including a short description for each error type. See the Microsoft.Office.Project.Server.Library.PSErrorID enumeration as well.

April 18, 2016

“Cannot find an SPSite object” Error From PowerShell

Filed under: PowerShell, SP 2013 — Tags: , — Peter Holpar @ 22:41

Last week I learned something new about a well known error message. We’ve tried to access a SharePoint site collection / site via the Get-SPSite / Get-SPWeb cmdlets in a test environment, but got the errors:

Get-SPWeb : Cannot find an SPSite object that contains the following Id or Url: http://YourSharePoint

and

Get-SPSite : Cannot find an SPSite object that contains the following Id or Url: http://YourSharePoint

In my practice this means either you mistyped the URL (what the message really suggests), using the wrong protocol (HTTP instead of  HTTPS or the opposite one), or your user has not the required permission (like membership in the the SharePoint_Shell_Access role, you can add via the Add-SPShellAdmin cmdlet) in the content database. In this case the URL was right, and I have db_owner permissions on the DBs, so I had to look for another reason.

Calling the Get-SPWebApplication cmdlet returned the URL of  every web applications in the farm, however when I tried to list the site collections in any (!) of these web applications, like this:

$wa = Get-SPWebApplication http://YourSharePoint
$wa.Sites

it gave me this error on the screen:

An error occurred while enumerating through a collection: The HTTP service
located at
http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc is
unavailable.  This could be because the service is too busy or because no
endpoint was found listening at the specified address. Please ensure that the
address is correct and try accessing the service again later..

Similarly, by checking the stack trace (via the value of  $StackTrace) after invoking the Get-SPSite / Get-SPWeb cmdlets, I received an other evidence of the problem with the communication with token service:

   at System.Net.HttpWebRequest.GetResponse()
   at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.Http
ChannelRequest.WaitForReply(TimeSpan timeout)

The corresponding details from ULS logs:

Entering BeginProcessing Method of Get-SPSite.
Leaving BeginProcessing Method of Get-SPSite.
Entering ProcessRecord Method of Get-SPSite.
SecurityTokenServiceSendRequest: RemoteAddress: ‘http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc&#8217; Channel: ‘Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustChannelContract’ Action: ‘http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue&#8217; MessageId: ‘urn:uuid:b7dfe754-fdd4-4177-bec6-f8992a097a6b’
SPSecurityContext: Request for security token failed with exception: System.ServiceModel.ServerTooBusyException: The HTTP service located at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc is unavailable.  This could be because the service is too busy or because no endpoint was found listening at the specified address. Please ensure that the address is correct and try accessing the service again later. —> System.Net.WebException: The remote server returned an error: (503) Server Unavailable.     at System.Net.HttpWebRequest.GetResponse()     at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)     — End of inner exception stack trace —    Server stack trace:      at System.ServiceModel.Chann…
…els.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)     at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)     at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)     at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)    Exception rethrown at [0]:      at System.Runtime.Remoting….
…Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)     at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)     at Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustContract.Issue(Message message)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst, RequestSecurityTokenResponse& rstr)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst)     at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForContext(Uri context, Boolean bearerToken, SecurityToken onBehalfOf, SecurityToken actAs, SecurityToken delegateTo, SPRequestSecurityTokenProperties properties)
An exception occurred when trying to issue security token: The HTTP service located at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc is unavailable.  This could be because the service is too busy or because no endpoint was found listening at the specified address. Please ensure that the address is correct and try accessing the service again later..
Microsoft.SharePoint.PowerShell.SPCmdletPipeBindException: Cannot find an SPSite object with Id or Url: http://YourSharePoint. —> System.ServiceModel.ServerTooBusyException: The HTTP service located at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc is unavailable.  This could be because the service is too busy or because no endpoint was found listening at the specified address. Please ensure that the address is correct and try accessing the service again later. —> System.Net.WebException: The remote server returned an error: (503) Server Unavailable.     at System.Net.HttpWebRequest.GetResponse()     at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)     — End of inner exception stack…
… trace —    Server stack trace:      at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)     at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)     at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)     at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage mess…
…age)    Exception rethrown at [0]:      at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)     at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)     at Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustContract.Issue(Message message)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst, RequestSecurityTokenResponse& rstr)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst)     at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForContext(Uri context, Boolean bearerToken, SecurityToken onBehalfOf, SecurityToken actAs, SecurityToken delegateTo, SPRequestSecurityTokenProperties properties)     at Microsoft.SharePoi…
…nt.SPSecurityContext.SecurityTokenForLegacyLoginContext(Uri context)     at Microsoft.SharePoint.SPSite.InitUserToken(SPRequest request)     at Microsoft.SharePoint.SPSite.SPSiteConstructor(SPFarm farm, Guid applicationId, Guid contentDatabaseId, Guid siteId, Guid siteSubscriptionId, SPUrlZone zone, Uri requestUri, String serverRelativeUrl, Boolean hostHeaderIsSiteName, SPUserToken userToken, Boolean appWebRequest, String appHostHeaderRedirectDomain, String appSiteDomainPrefix, String subscriptionName, String appSiteDomainId, Uri primaryUri)     at Microsoft.SharePoint.SPSite..ctor(SPFarm farm, Uri requestUri, Boolean contextSite, Boolean swapSchemeForPathBasedSites, SPUserToken userToken)     at Microsoft.SharePoint.SPSite..ctor(SPFarm farm, Uri requestUri, Boolean contextSite, SPUserToke…
…n userToken)     at Microsoft.SharePoint.SPSite..ctor(String requestUrl)     at Microsoft.SharePoint.PowerShell.SPSitePipeBind.Read(Boolean exactUrl)     — End of inner exception stack trace —     at Microsoft.SharePoint.PowerShell.SPSitePipeBind.Read(Boolean exactUrl)     at Microsoft.SharePoint.PowerShell.SPCmdletGetSite.InternalValidate()     at Microsoft.SharePoint.PowerShell.SPCmdlet.ProcessRecord()
Error Category: InvalidData    Target Object  Microsoft.SharePoint.PowerShell.SPCmdletGetSite  Details  NULL  RecommendedAction NULL
Leaving ProcessRecord Method of Get-SPSite.
Entering EndProcessing Method of Get-SPSite.
Leaving EndProcessing Method of Get-SPSite.

Checking the web server I found that the application pool “SecurityTokenServiceApplicationPool” was accidentally stopped, causing issues with the authentication. Restarting it solved my problem.

After solving the issue I played further with the token service, and stopped the web site for "SharePoint Web Services" in IIS, just to learn how the error messages differ in this case. Here is what I found.

The message for the failure of the the Get-SPSite / Get-SPWeb cmdlets is the same as the original case, however the stack trace ($StackTrace) is an other one:

   at System.Management.Automation.Internal.PipelineProcessor.SynchronousExecut
eEnumerate(Object input, Hashtable errorResults, Boolean enumerate)
   at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boo
lean ignoreInput, CommandParameterInternal[][] pipeElements, CommandBaseAst[] p
ipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext fun
cContext)
   at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(Inte
rpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.
Run(InterpretedFrame frame)

Trying to enumerate the site collection in the web application (see above) results in this error:

An error occurred while enumerating through a collection: There was no
endpoint listening at
http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc that
could accept the message. This is often caused by an incorrect address or SOAP
action. See InnerException, if present, for more details..
At line:1 char:1
+ $wa.Sites
+ ~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Microsoft.Share…rePoint.SPS
   ite]:SPEnumerator`1) [], RuntimeException
    + FullyQualifiedErrorId : BadEnumeration

And finally the ULS trace for this scenario (as you can see this time for Get-SPWeb instead of Get-SPSite) :

Entering BeginProcessing Method of Get-SPWeb.
Leaving BeginProcessing Method of Get-SPWeb.
Entering ProcessRecord Method of Get-SPWeb.
SecurityTokenServiceSendRequest: RemoteAddress: ‘http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc&#8217; Channel: ‘Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustChannelContract’ Action: ‘http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue&#8217; MessageId: ‘urn:uuid:bc713eb8-c1f1-453c-b333-86ef8e4411a5’
SPSecurityContext: Request for security token failed with exception: System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. —> System.Net.WebException: Unable to connect to the remote server —> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:32843     at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)     at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address,…
… ConnectSocketState state, IAsyncResult asyncResult, Exception& exception)     — End of inner exception stack trace —     at System.Net.HttpWebRequest.GetRequestStream(TransportContext& context)     at System.Net.HttpWebRequest.GetRequestStream()     at System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.GetOutputStream()     — End of inner exception stack trace —    Server stack trace:      at System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.GetOutputStream()     at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)     at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout)     at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout) …
…    at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)     at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)    Exception rethrown at [0]:      at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)     at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)     at Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustContract.Issue(Message message)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst, Re…
…questSecurityTokenResponse& rstr)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst)     at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForContext(Uri context, Boolean bearerToken, SecurityToken onBehalfOf, SecurityToken actAs, SecurityToken delegateTo, SPRequestSecurityTokenProperties properties)
An exception occurred when trying to issue security token: There was no endpoint listening at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details..
Microsoft.SharePoint.PowerShell.SPCmdletPipeBindException: Cannot find an SPSite object that contains the following Id or Url: http://YourSharePoint. —> System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. —> System.Net.WebException: Unable to connect to the remote server —> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:32843     at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)     at System.Net.ServicePoint.Conne…
…ctSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception)     — End of inner exception stack trace —     at System.Net.HttpWebRequest.GetRequestStream(TransportContext& context)     at System.Net.HttpWebRequest.GetRequestStream()     at System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.GetOutputStream()     — End of inner exception stack trace —    Server stack trace:      at System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.GetOutputStream()     at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)     at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeo…
…ut)     at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)     at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)     at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)    Exception rethrown at [0]:      at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)     at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)     at Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustContract.Issue(Message message) …
…    at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst, RequestSecurityTokenResponse& rstr)     at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst)     at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForContext(Uri context, Boolean bearerToken, SecurityToken onBehalfOf, SecurityToken actAs, SecurityToken delegateTo, SPRequestSecurityTokenProperties properties)     at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForLegacyLoginContext(Uri context)     at Microsoft.SharePoint.SPSite.InitUserToken(SPRequest request)     at Microsoft.SharePoint.SPSite.SPSiteConstructor(SPFarm farm, Guid applicationId, Guid contentDatabaseId, Guid siteId, Guid siteSubscriptionId, SPUrlZone zone, Uri requestUri, String s…
…erverRelativeUrl, Boolean hostHeaderIsSiteName, SPUserToken userToken, Boolean appWebRequest, String appHostHeaderRedirectDomain, String appSiteDomainPrefix, String subscriptionName, String appSiteDomainId, Uri primaryUri)     at Microsoft.SharePoint.SPSite..ctor(SPFarm farm, Uri requestUri, Boolean contextSite, Boolean swapSchemeForPathBasedSites, SPUserToken userToken)     at Microsoft.SharePoint.SPSite..ctor(SPFarm farm, Uri requestUri, Boolean contextSite, SPUserToken userToken)     at Microsoft.SharePoint.SPSite..ctor(String requestUrl)     at Microsoft.SharePoint.PowerShell.SPSitePipeBind.Read(Boolean exactUrl)     — End of inner exception stack trace —     at Microsoft.SharePoint.PowerShell.SPSitePipeBind.Read(Boolean exactUrl)     at Microsoft.SharePoint.PowerShell.SPCmdletGetW…
…eb.InternalValidate()     at Microsoft.SharePoint.PowerShell.SPCmdlet.ProcessRecord()
Error Category: InvalidData    Target Object  Microsoft.SharePoint.PowerShell.SPCmdletGetWeb  Details  NULL  RecommendedAction NULL
Leaving ProcessRecord Method of Get-SPWeb.
Entering EndProcessing Method of Get-SPWeb.
Leaving EndProcessing Method of Get-SPWeb.

Hopefully these details help someone to solve the issue easier and faster when it comes.

Re-creating the Missing Out-of-the-Box Search Locations using PowerShell

Filed under: PowerShell, Reflection, Search, SP 2013 — Tags: , , , — Peter Holpar @ 22:40

Recently we migrated a SharePoint 2010 web application into an existing SharePoint 2013 farm to free up the old SharePoint 2010 farm for “recycling” (it was the last application running in the old farm). The web application has a single site collection, and contains a single business application with views and list forms having a lot of customizations via “jQuery magic”. Since a replacement of the business application is planed for the near future, we decided not to upgrade the site collection to the SharePoint 2013 user interface (version 15). Leaving it in the SharePoint 2010 mode (version 14) ensures the views and forms are working further without any modifications in the JavaScript codes. After a few days a user complained, that when searching the local web site an error is displayed instead of the search results on the _layouts/OSSSearchResults.aspx page:

Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Microsoft SharePoint Foundation-compatible HTML editor such as Microsoft SharePoint Designer. If the problem persists, contact your Web server administrator

In the ULS logs we found these entries:

CoreResultsWebpart: Couldnt find location with internal name LocalSearchIndex
CoreResultsDatasourceView: Couldnt find location with internal name LocalSearchIndex

On the web we found a post from Sushant Dukhande with the description of the issue, and a suggestion for the solution.

Using the script on that site it turned out, that the LocationConfigurations property of the search proxy is really empty. In an other environment, where we tested the migration we had no such issue.

Sushant Dukhande suggests to re-provision the search application. It might really solve the problem, however in our case I felt it to be an intense change, and searched for an alternative solution. Having a look into what happens under the cover of a provisioning process, I found the method responsible for provisioning the missing search locations. It is the internal static CreateOOBLocations method of the Microsoft.Office.Server.Search.Administration.LocationFactory class (in the Microsoft.Office.Server.Search assembly).

First, we need a reference to the search service application. You can get it like this (assuming it is named "Search Service Application"):

$ssa = Get-SPEnterpriseSearchServiceApplication "Search Service Application"

or via this script (as long as you are sure, you have a single instance of this service application type in your farm):

[Microsoft.Office.Server.Search.Administration.SearchServiceApplication]$ssa = Get-SPServiceApplication | ? { $_.TypeName -eq "Search Service Application" }

To display the names of the existing search locations:

$locConfigs = $ssa.LocationConfigurations
$locConfigs | % { $_.InternalName }

The following PowerShell script shows how to invoke the CreateOOBLocations method passing the search service application as parameter using PowerShell and Reflection:

$searchAssembly = [Microsoft.Office.Server.Search.Administration.SearchServiceApplication].Assembly
$locationFactory_Type = $searchAssembly.GetType("Microsoft.Office.Server.Search.Administration.LocationFactory")

$bindingFlags = [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Static
$mi_CreateOOBLocations = $locationFactory_Type.GetMethod("CreateOOBLocations", $bindingFlags)
$mi_CreateOOBLocations.Invoke($null, @([Microsoft.Office.Server.Search.Administration.SearchServiceApplication]$ssa))

Invoking the CreateOOBLocations method might be not always the solution for you. The same is true for the re-provisioning process suggested by Sushant Dukhande, since it invokes the same method as well. The problem, that this method has a condition, before provisioning all of the default search locations:

if (searchApp.LocationConfigurations.Count < 1)
{
    LocationConfigurationCollection locationConfigurationsInternal = searchApp.GetLocationConfigurationsInternal(true);
    CreateLiveLocation(locationConfigurationsInternal);
    CreateLiveSuggestionsLocation(locationConfigurationsInternal);
    CreateLocalSharepointLocation(locationConfigurationsInternal);
    CreateLocalPeopleLocation(locationConfigurationsInternal);
    CreateLocalFSSharePointLocation(locationConfigurationsInternal);
}

I don’t see, how our farm “lost” its search locations, but if it is possible to “lose” only a subset of the search locations (for example, only the one called LocalSearchIndex), it won’t be re-created by the CreateOOBLocations method, as the count of search location is still not zero.

In this case, the solution may be to re-create only the missing search location via the corresponding method. In the case of the LocalSearchIndex search location it is the CreateLocalSharepointLocation method of the LocationFactory class:

$locConfigs = $ssa.LocationConfigurations
$mi_CreateLocalSharepointLocation = $locationFactory_Type.GetMethod("CreateLocalSharepointLocation", $bindingFlags)
$mi_CreateLocalSharepointLocation.Invoke($null, @([Microsoft.Office.Server.Search.Administration.LocationConfigurationCollection]$locConfigs))

After fixing the issue in the farm, I’ve tested our other farms as well to find out, whether they are affected by the same problem or not. In one of the farm, the script provided in the post I mentioned earlier detected the issue, although I was sure, there is no problem with the search. It turned out to be a false positive test. This farm has its search service as a shared service from another farm, and the user account the script was run with had no permission on the search service in that remote farm. The script simply hid away the access denied error.

However, if we create a LocationConfigurationCollection instance via its internal constructor (either with a parameter of type SearchServiceApplication or of type SearchServiceApplicationProxy), the access denied error is displayed in the case the user has no permissions, and the items of the collection can be accessed if there is no problem with the permissions.

Let’s see first the script using the SearchServiceApplication:

$bindingFlags = [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Instance
$ci_LocationConfigurationCollection = [Microsoft.Office.Server.Search.Administration.LocationConfigurationCollection].GetConstructor($bindingFlags, $null, @([Microsoft.Office.Server.Search.Administration.SearchServiceApplication]), $null)
$locConfigs = $ci_LocationConfigurationCollection.Invoke(@([Microsoft.Office.Server.Search.Administration.SearchServiceApplication]$ssa))
$locConfigs | % { $_.InternalName }

As I wrote, you can achieve the same via a service proxy. It is useful for example, if the application is connected to a shared search service of another farm. First, we get the proxy as:

$url = "http://YourSharePointApp/&quot;
$site = Get-SPSite $url
$serviceContext = [Microsoft.SharePoint.SPServiceContext]::GetContext($site)
$ssaAppProxy = $serviceContext.GetDefaultProxy([Microsoft.Office.Server.Search.Administration.SearchServiceApplicationProxy])

Next, we can use the same script as earlier, but in this case we invoke the internal constructor having the SearchServiceApplicationProxy parameter type:

$bindingFlags = [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Instance
$ci2_LocationConfigurationCollection = [Microsoft.Office.Server.Search.Administration.LocationConfigurationCollection].GetConstructor($bindingFlags, $null, @([Microsoft.Office.Server.Search.Administration.SearchServiceApplicationProxy]), $null)
$locConfigs = $ci2_LocationConfigurationCollection.Invoke(@([Microsoft.Office.Server.Search.Administration.SearchServiceApplicationProxy]$ssaAppProxy))
$locConfigs | % { $_.InternalName }

March 15, 2016

How to get the value of a Project Server Enterprise Custom Field via the Project Server Managed Client Object Model

Filed under: Managed Client OM, Project Server — Tags: , — Peter Holpar @ 22:20

About two years ago I posted a code about how to set the value of a Project Server Enterprise Field via the managed client OM. Again and again I get the question how to get the value, once it is set already.

In the first case I assume, you already know the ID of your project and the internal name of the field you would like to query. In this case, you need only send a single request to the server, as shown in this code:

  1. var url = @"http://YourProjectServer/pwa&quot;;
  2. var projectContext = new ProjectContext(url);
  3.  
  4. var projId = new Guid("98138ffd-d0fa-e311-83c6-005056b45654");
  5. var cfInternalName = "Custom_b278fdf35d16e4119568005056b45654";
  6.  
  7. var proj = projectContext.Projects.GetByGuid(projId);
  8. projectContext.Load(proj, p => p[cfInternalName], p => p.Name);
  9.  
  10. projectContext.ExecuteQuery();
  11.  
  12. Console.WriteLine(proj.Name, proj.FieldValues[cfInternalName]);

If either the ID of your project or the internal name of the field is unknown, you need an extra round-trip before the query shown in the previous code to determine their value. In the code below I assume you know none of these values:

  1. var url = @"http://YourProjectServer/pwa&quot;;
  2. var projectContext = new ProjectContext(url);
  3. var projName = "Your Project Name";
  4. var fieldName = "NameOfTheField";
  5.  
  6. projectContext.Load(projectContext.Projects, ps => ps.Include(p => p.Id, p => p.Name));
  7. projectContext.Load(projectContext.CustomFields, cfs => cfs.Include(cf => cf.InternalName, cf => cf.Name));
  8. projectContext.ExecuteQuery();
  9.  
  10. var projId = projectContext.Projects.First(p => p.Name == projName).Id;
  11. var cfInternalName = projectContext.CustomFields.First(cf => cf.Name == fieldName).InternalName;
  12.  
  13. var proj = projectContext.Projects.GetByGuid(projId);
  14. projectContext.Load(proj, p => p[cfInternalName], p => p.Name);
  15.  
  16. projectContext.ExecuteQuery();
  17.  
  18. Console.WriteLine(proj.Name, proj.FieldValues[cfInternalName]);

I hope it helps to read the custom field values, for example the values set by the code in the former post.

March 3, 2016

Access Denied when Setting Group Properties as Group Owner

Filed under: Bugs, Security, SP 2013 — Tags: , , — Peter Holpar @ 22:10

We have a SharePoint group whose members should administer group membership of other, business-related SharePoint groups. We assigned this group as a Group Owner to the other groups, thus they have the required permissions to group administration.

This week one of the group administrators was to change the description of one of the groups. Actually, it is not part they mission, however I don’t see any problem with that. SharePoint itself defines the rule of the group owner on the Change Group Settings page:

The owner can change anything about the group such as adding and removing members or deleting the group.

Yes, they can even delete the group, I have tested it.

However in this case the member of the group owner group received an Access Denied error, when he submitted the changes to the server. To be able to understand the reason, let’s first see the corresponding ULS logs:

03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           8e2s    Medium      Unknown SPRequest error occurred. More information: 0x80070005    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           aix9j    High        SPRequest.AddOrUpdateItem: UserPrincipalName=i:0).w|s-1-5-21-1613396233-3282607421-4023646941-2481, AppPrincipalName= ,bstrUrl=
http://YourSharePoint ,bstrListName={F0FF0E7F-61FE-4BBF-993A-2F396E44E133} ,bAdd=False ,bSystemUpdate=False ,bPreserveItemVersion=False ,bPreserveItemUIVersion=False ,bUpdateNoVersion=False ,pbstrNewDocId=00000000-0000-0000-0000-000000000000 ,bHasNewDocId=False ,bstrVersion=23 ,bCheckOut=False ,bCheckin=False ,bUnRestrictedUpdateInProgress=False ,bMigration=False ,bPublish=False ,bstrFileName=<null>    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)), StackTrace:    at Microsoft.SharePoint.SPListItem.AddOrUpdateItem(Boolean bAdd, Boolean bSystem, Boolean bPreserveItemVersion, Boolean bNoVersion, Boolean bMigration, Boolean bPublish, Boolean bCheckOut, Boolean bCheckin, Guid newGuidOnAdd, Int32& ulID, Object& objAttachmentNames, Object& objAttachmentContents, Boolean suppressAfterEvents, String filename, Boolean bPreserveItemUIVersion)     at Microsoft.SharePoint.SPListItem.UpdateInternal(Boolean bSystem, Boolean bPreserveItemVersion, Guid newGuidOnAdd, Boolean bMigration, Boolean bPublish, Boolean bNoVersion, Boolean bCheckOut, Boolean bCheckin, Boolean suppressAfterEvents, String filename, Boolean bPreserveItemUIVersion)     at …    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      …Microsoft.SharePoint.SPListItem.Update()     at Microsoft.SharePoint.ApplicationPages.CBaseNewGroup.UpdateAdditionalProperties(Int32 groupId)     at Microsoft.SharePoint.ApplicationPages.EditGroup.DoOperation()     at Microsoft.SharePoint.ApplicationPages.CBaseNewGroup.BtnOK_Click(Object sender, EventArgs e)     at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)     at System.Web.UI.Page.ProcessRequest()     at System.Web.UI.Page.ProcessRequest(HttpContext context)     at System.Web.HttpApplication.CallHandlerExecutionStep.Syste…    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      …m.Web.HttpApplication.IExecutionStep.Execute()     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)     at System.Web.HttpApplication.PipelineStepManager.ResumeSteps(Exception error)     at System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb)     at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.UnsafeII…    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ai1wu    Medium      …SMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)     at System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)      e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ftd0    Medium      Access Denied. Exception: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)), StackTrace:   at Microsoft.SharePoint.Library.SPRequestInternalClass.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bPreserveItemUIVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bUnRestrictedUpdateInProgress, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback)  …    e552649d-8539-f075-b98b-accc2d7bd4f5
03/01/2016 17:27:28.61*    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             General                           ftd0    Medium      …   at Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bPreserveItemUIVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bUnRestrictedUpdateInProgress, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback).    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             Micro Trace                       uls4    Medium      Micro Trace Tags: 0 nasq,6 agb9s,18 ak8dj,12 b4ly,0 b4ly,41 aix9j,0 ai1wu,0 ftd0    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             Monitoring                        b4ly    Medium      Leaving Monitored Scope (Request (POST:
http://YourSharePoint/_layouts/15/editgrp.aspx?Group=YourGroup&Source=http%3A%2F%2FYourSharePoint%2F%5Flayouts%2F15%2Fpeople%2Easpx%3FMembershipGroupId%3D6650)). Execution Time=81,9053366451775    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x439C    SharePoint Foundation             Claims Authentication             amge7    Medium      SPFederationAuthenticationModule.IsRedirectToLogOnPage: Detected a redirection but the redirect is not to a known signin page:
http://YourSharePoint/_layouts/15/AccessDenied.aspx    e552649d-8539-f075-b98b-accc2d7bd4f5
03.01.2016 17:27    w3wp.exe (0x339C)                           0x49EC    SharePoint Foundation             Monitoring                        nasq    Medium      Entering monitored scope (Request (GET:
http://YourSharePoint/_layouts/15/AccessDenied.aspx)). Parent No    

As you can see, on the page editgrp.aspx we have an UnauthorizedAccessException: Access is denied. However, at first it seems to be not group-related, as it is thrown in the SPListItem.AddOrUpdateItem method. What’s that?

As I am aware of that the rich text description of a group is stored in the Notes field corresponding list item in the hidden user information list,and not in the SPGroup object itself, at this point I had already an idea, what might be the reason of the the issue. But let’s prove that scientifically!

Later in the stack trace we found the UpdateAdditionalProperties method of the Microsoft.SharePoint.ApplicationPages.CBaseNewGroup class, called by the DoOperation method of the Microsoft.SharePoint.ApplicationPages.EditGroup class.

The DoOperation method updates group properties stored in the SPGroup object itself, but the UpdateAdditionalProperties method updates the information stored in the list item fields in the user information list, like the description of the group, and other fields requested in optional query string parameters:

  1. protected void UpdateAdditionalProperties(int groupId)
  2. {
  3.     SPListItem itemById = base.Web.SiteUserInfoList.GetItemById(groupId);
  4.     string text = this.txtGrpDescription.Text;
  5.     itemById["Notes"] = text;
  6.     int num = 1;
  7.     while (true)
  8.     {
  9.         string str2 = base.Request.QueryString["FieldName" + num.ToString(CultureInfo.InvariantCulture)];
  10.         if (string.IsNullOrEmpty(str2))
  11.         {
  12.             break;
  13.         }
  14.         string str3 = base.Request.QueryString["FieldValue" + num.ToString(CultureInfo.InvariantCulture)];
  15.         itemById[str2] = str3;
  16.         num++;
  17.     }
  18.     itemById.Update();
  19. }

A minor detour about the user information list. As you know, it is a hidden list on the SharePoint site collection root web site, and typically accessed via the URL http://YourSharePoint/_catalogs/users/simple.aspx. However, it is a common misunderstanding, even in case of well-known SharePoint experts, that “This list is only visible to and accessible by administrators”. Based on my experience, it is true only for the webpage (_catalogs/users/simple.aspx), but not for the list itself. The list inherits its permissions from the root site, meaning anyone having read permission on the root site, can access the list.

For example, simply by using REST:

http://YourSharePoint/_api/Web/SiteUserInfoList

or (assuming the Guid is the ID of the list):

http://YourSharePoint/_api/Lists/GetById(‘6749e2d2-ca87-445f-8fc1-b7f7a4e410ad&#8217;)

However

http://YourSharePoint/_api/Lists/GetByTitle(‘User%20Information%20List&#8217;)

does not work, because it is a hidden list.

One can even list all site users via the URL:

http://YourSharePoint/_api/Web/SiteUserInfoList/Items?$select=Title,Name

To be sincerely, I’m not sure if it is a feature or simply a security vulnerability. I’ve also tried to update the list items of the user list from the client side using the managed client object model by the credentials of a user having write permission on the site, but up to know I have not “succeeded”, received access denied. But I don’t give up. Winking smile

After the detour, let’s back to our issue. Since in our case the user had no write permissions on the root site (and thus no write permissions on the list and the list items), the UpdateAdditionalProperties method was not able to set the group description (the Notes field of the list item) and resulted in the Access Denied error. Other members of the same owner group, having the write permission on the root site level, have no such problems.

It is important to point out, that even if the user has no write permission and get the Access Denied error, other changes in the group configuration (like the Name or “Who can edit the membership of the group“) are updated for the group, as they are already saved by the DoOperation method before the exception was thrown by the UpdateAdditionalProperties method. You can prove that by navigating back to the Change Group Settings page, and reload it in browser via F5. The same is true, when one sets the group properties by code, like the managed or JavaScript client object model or by REST request. The owner can change group properties stored in the SPGroup object without error, but not the list item in the user list.

I consider this behavior to be a bug, and I think the UpdateAdditionalProperties method should include an elevated privileges code block to enable group owners to change the properties stored in the list item fields as well.

That’s all I wanted to share about the error itself, but if you have time, you can read on for another story.

There is an other misunderstanding I read in other SharePoint blogs while researching the issue. One may think, that setting a group as its own group owner (Solution 1) and selecting the “Group Members” option for “Who can edit the membership of the group“ (Solution 2) are interchangeable (see Scott Baitz stating his Solution 2 “will provide the same functionality as the solution above”, Solution 1). That is definitely false. Let’s see the difference, starting with Solution 2, then comparing the extra permissions one get via Solution 1.

Solution 2

If you allow group members to edit group membership by implementing Solution 2, the group members can add other users to the group or remove users, even themselves from the group. (Note: If they removed themself  from the group, that action would be of course not reversible. They can not add themself back to the group, only if they have that permission via the group owner group or they have other administrative permission, like site collection administrator, or full control permission on the site.)

The group members have access to the page People and Groups : YourGroup (http://YourSharePoint/_layouts/15/people.aspx?MembershipGroupId=4600, assuming 4600 is the ID of your group). where they can list and change the group membership.

On this page they have the following options:

New menu:
Add Users

Actions menu:
E-Mail Users
Call/Message Selected Users
Remove Users from Group

Settings menu:
View Group Permissions

The group members can not edit group properties. The “Group Settings” is not displayed in the Settings menu, and even if they try to access the page by its URL (like http://YourSharePoint//_layouts/15/editgrp.aspx?Group=YourGroup), they get an error message:

Only owners of the group "YourGroup" can change its settings

(Actually, the permissions are checked in the InitPage method of the Microsoft.SharePoint.ApplicationPages.EditGroup class, by invoking the CanCurrentUserManageGroup method of the Microsoft.SharePoint.SPGroup class.)

Solution 1

As a group owner, you have the same options as in the case of Solution 1, and additionally you have the “Group Settings” in the Settings menu to access the People and Groups : Change Group Settings page and edit group properties. (Of course, if you remove yourself or your group from the Group Owner field, and you have no other extra permission on the site, you can not undo this action. It’s the same as above in Solution 2 with group membership.). If the group owner has no write permission of the root site, an Access Denied is thrown when setting group properties via the page, see the original issue in this post.

Finally, when a user browses the groups in a site, he can found that the “View Group Permissions” is displayed in the  Settings menu for one group, but it is not available for the other group. This option (as you can expect) is again permission dependent. The permissions are checked in the OnLoad method of the Microsoft.SharePoint.ApplicationPages.PeoplePage class (the code behind of the people.aspx page). The “View Group Permissions” option is visible, if the DoesCurrentUserHavePermission method of the Microsoft.SharePoint.WebControls.GroupPermissions class returns true:

internal static bool DoesCurrentUserHavePermission(SPWeb web, SPGroup group)
{
    if (!group.ContainsCurrentUser && !group.CanCurrentUserEditMembership)
    {
        return false;
    }
    return true;
}

(Note that the method has two parameters. The first one is of type SPWeb but that is not used at all in the method.)

That means, the “View Group Permissions” option is displayed only if the user is member of the group or can edit the group membership. On the other side, if the user can access the people.aspx page for the group, and see the group membership only because the group is configured to allow “Everyone” as “Who can view the membership of the group“, this option is not available. With other words, a user that is not in the group itself, not the owner of the group, and have no admin rights are not allowed to see the group permissions.

February 27, 2016

Test On Demand PDF Conversion without Document Libraries via PowerShell

Filed under: PowerShell, SP 2013, Word Automation Services — Tags: , , — Peter Holpar @ 15:52

If you had to convert Word documents to PDFs using the Word Automation Services in SharePoint 2010, you had to store the files at least temporary in document libraries. That was true, even if you have created the documents dynamically, for example based on SharePoint list item data using the Open XML SDK and sent as response to a web request (as illustrated in this example). Typically you could solve this requirement using streams or byte arrays otherwise, but the ConversionJob class supported in SharePoint 2010 only working with files stored in document libraries via the AddFile method. A further limitation of that only asynchronous conversation was supported, if you needed a synchronous operation, you should have to find workaround, like this one.

In SharePoint 2013 we have the SyncConverter class, that – as its name already suggests – provides the synchronous conversion as well stream and byte array support via its Convert method.

Recently we are working on a document conversation solution again (similar to the SharePoint 2010 example I referred to above), but we did not want to deploy the SharePoint solution each time we change the logic in the Word document creation, but still wanted to have the ability to test:

  • If the WAS is able to convert the word document to PDF.
  • Does the output of the conversation fulfill our expectations.
  • How much time the conversion takes.

So we called the appropriate methods of our Word document creation assembly from PoweShell, and stored the result in a byte array. This step is specific to the current customer project and as such out of the scope of this post. Instead of this we simulate the step simply by reading the static content of a .docx file in the script below. Next we got a reference to the Word Automation Services Proxy, and converted the Word document from the byte array into another byte array, holding the content of the PDF conversion. In the original SharePoint solution this byte array would be sent back in the web server response to the client. In the script we simply save it into a file into the file system. If you run this script, don’t forget, that if the target file already exists, it is overwritten without any confirmation request!

  1. $url = "http://YourSharePointSite&quot;
  2. $inputFilePath = "C:\Data\input.docx"
  3. $outputFilePath = "C:\Data\output.pdf"
  4.  
  5. [byte[]]$inputContentBytes = Get-Content $inputFilePath -Encoding Byte
  6. [byte[]]$outputContentBytes = $null
  7.  
  8. # get the WAS proxy assigned to the site
  9. $site = Get-SPSite $url
  10. $serviceContext = [Microsoft.SharePoint.SPServiceContext]::GetContext($site)
  11. $wasAppProxy = $serviceContext.GetDefaultProxy([Microsoft.Office.Word.Server.Service.WordServiceApplicationProxy])
  12. # or if you have a single WAS proxy in your farm you can use this as well:
  13. #$wasAppProxy = Get-SPServiceApplicationProxy | ? { $_.TypeName -eq "Word Automation Services Proxy" }
  14.  
  15. [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Word.Server")
  16. $job = New-Object Microsoft.Office.Word.Server.Conversions.SyncConverter($wasAppProxy)
  17. $job.Settings.OutputFormat = "PDF"
  18. $job.Settings.UpdateFields = $true
  19. $job.UserToken = ($site.RootWeb).CurrentUser.UserToken
  20.  
  21. $jobInfo = $job.Convert($inputContentBytes, [ref] $outputContentBytes)
  22. [System.IO.File]::WriteAllBytes($outputFilePath, $outputContentBytes)
  23.  
  24. If ($jobInfo.Succeeded)
  25. {
  26.   $duration = ($jobInfo.CompleteTime $jobInfo.StartTime).TotalSeconds
  27.   Write-Host Job completed in $duration seconds
  28. }
  29. Else
  30. {
  31.   Write-Host Job completed with error $jobInfo.ErrorCode
  32. }

You should have the Word Automation Services infrastructure (I mean the service application and the related services) up and running. I was sure, that it is OK in our case, but wanted to keep the script simple, so I did not check these things in the script above. If you are not sure that your farm was set up correctly, you should have a look at this script and extend my version with the test steps if you wish.

Getting the Item Count of all Lists of all Sub-Sites via a Single Request from Client Code

Filed under: Managed Client OM, OData, Project Server, SP 2013 — Tags: , , , — Peter Holpar @ 14:43

Recently I had a task to get the item count of all lists of all Project Web Sites (PWS) of a Project Server instance from a client-side application. Note, that the PWSs are located directly under the Project Web Access (PWA) site, so there is no deeper site structure in this task to deal with, so I was pretty sure that it can be achieved in a single request. Although in my case the task was Project Server related, one can use the same method in the case of SharePoint Server as well, it is only important, that you should not have a multiple level site structure, for a deeper site structure this method simply does not work.

I show you both the REST (OData) and the managed client object model approach. Let’s start with the client OM sample:

  1. string siteUrl = "http://YourProjectServer/PWA&quot;;
  2. using (var clientContext = new ClientContext(siteUrl))
  3. {
  4.     var rootWeb = clientContext.Web;
  5.  
  6.     clientContext.Load(rootWeb, rw => rw.Webs.Include(w => w.Title, w => w.ServerRelativeUrl, w => w.Lists.Include(l => l.Title, l => l.ItemCount)));
  7.     clientContext.ExecuteQuery();
  8.  
  9.     foreach(var web in rootWeb.Webs)
  10.     {
  11.         if (web.Lists.Any())
  12.         {
  13.             Console.WriteLine("Lists of web '{0}' [{1}]", web.Title, web.ServerRelativeUrl);
  14.             foreach (var list in web.Lists)
  15.             {
  16.                 Console.WriteLine("'{0}' [Item count: {1}]", list.Title, list.ItemCount);
  17.             }
  18.         }
  19.     }
  20. }

The corresponding REST query can be submitted as a GET request sent to this URL:

http://YourProjectServer/PWA/_api/web/webs?$expand=Lists&$select=ServerRelativeUrl,Title,Lists/ItemCount,Lists/Title

If you need the item count only from a specific list (for example, the lists with title ‘Risks’) for all subsites, you can easily achieve that in the client OM sample by including a Where clause in the query:

clientContext.Load(rootWeb, rw => rw.Webs.Include(w => w.Title, w => w.ServerRelativeUrl, w => w.Lists.Include(l => l.Title, l => l.ItemCount).Where(l => l.Title == "Risks")));

The corresponding REST query would be:

http://YourProjectServer/PWA/_api/web/webs?$expand=Lists&$filter=Lists/Title eq ‘Risks’&$select=ServerRelativeUrl,Title,Lists/ItemCount,Lists/Title

However, when submitting this request I get a response with status HTTP 400 and the message: The field or property ‘Title’ does not exist.

I’m working on a solution and update this post as soon as I found one. Feel free to help me by sending it as a comment. Winking smile

February 17, 2016

Visual Studio Build Error: Could not copy the file because it was not found

Filed under: Bugs, Visual Studio — Tags: , — Peter Holpar @ 22:03

Recently we got a bunch of errors when building a project in Visual Studio 2013, complaining about missing content files:

Could not copy the file "C:\projects\OurProject\images\image1.png" because it was not found.

We checked the files in the file system, but they have really disappeared. We had luck, as we had the original files under source control, so we could restore them from the repository. But it did not provide a long-term solution, as we got the very same error on the next build, so we had to restore them again. Restarting Visual Studio did not help as well.

To prohibit further deletion I set the files as read-only. It led me to the solution, as we got this time another build error message:

Unable to copy file "C:\projects\OurProject\images\image1.png" to ".\image1.png". Access to the path ‘.\image1.png’ is denied.

The problem was that the Output path (under project properties Build / Output) was accidentally deleted, so Visual Studio was to create the build at the source file location. Resetting the original value, the default bin/debug path resolved our issue.

One can simple reproduce the issue, simply create a new project (let it be a Console Application, for example), add an image as content file (Build Action = Content) to it and set its Copy to Output Directory property to Copy always. Then delete the content of the Output path property, save the project and try to build.

I know that it was our mistake, but to tell the truth, I would expect Visual Studio not to delete my source files, but rather validate my input, and not allow to leave the Output path empty.

Older Posts »

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 62 other followers