Un-Approve SharePoint List Item Previous Versions

I recently had a change request against a SharePoint Forms Library I had created a few years ago – the request was to adjust the permissions so that form submitters could see only the forms that they’ve submitted (and not others).

This is a generally straightforward action on new libraries: enable ” Require content approval for submitted items?”, and change “Who should see draft items in this document library?” to

However, enabling these settings seems to have caused the items that already existed to have an Approval Status of “Approved, ” despite a pending Approval workflow.  This caused the undesired effect of allowing users who do not hold the “Approve” permission level to access previous version of items still in the approval workflow.

I needed to reject previous versions of forms where the current version had not yet been approved.  On lots of items.

I found numerous examples from google how to use PowerShell to set the Approval Status of list items; however, nearly every example dealt with only the current version of a list item – making no mention of altering the approval status of previous versions of list items.

Additionally, I found a few posts attempting to manipulate attributes for previous versions;  the responses for each of these inquires were varied:

  • “you can’t – history is read-only,”
  • “you can migrate the documents to a new list, and re-build the history”
  • “you can delete the old versions”

I even found a mega-thread on TechNet how to “List and Delete List Item Versions using PowerShell,” and a “Complete Guide to Getting and Setting Fields Using PowerShell”

None of these options accomplished what I was seeking:  to simply remove the approval on previous versions.

Finally, I resorted to simply poking at the objects from PowerShell (never under-estimate the power of Get-Member to explore objects) Attempting to modify the properties on a previous version would yeild the error message “Unable to index into an object of type Microsoft.SharePoint.SPListItemVersion” (Link)

Ok – different approach:  I know my desired action is feasible via the UI for single list items:

So, I opened Chrome developer tools and captured the command sent when clicking “Reject this version”: A POST to “/_layouts/versions.aspx” with the ItemID, and an “op” value of “TakeOffline”. A quick google search revealed a server-side object model equilavent: Microsoft.SharePoint.SPFile.TakeOffline

My solution: invoke SPListItem.File.TakeOffline() for every file which is currently pending and has a previously approved version:

 

SharePoint 2013 List Workflows Failing

Quick Post.

Today I had an issue with a SharePoint 2013 List Workflow not running on a SharePoint Online Team Site.

 

Retrying last request. Next attempt scheduled in less than one minute. Details of last request: HTTP  to https://<SomeCoolTenant>.sharepoint.com/sites/<SomeCoolSite>/_api/web/lists(guid'**********************************') Correlation Id:  Instance Id: *************************************

System.Net.WebException: The request was aborted: The request was canceled. ---> System.InvalidOperationException: Failed to fetch an access token from the token service. The token service returned an error type of 'unauthorized_client' with the following description: AADSTS70001: Application with identifier '**************************' was not found in the directory **************************************
Trace ID: **********************************
Correlation ID: *****************************************
Timestamp: 2017-10-30 14:07:03Z ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at Microsoft.Activities.Hosting.Security.OAuthS2SSecurityTokenServiceCredential.FetchAccessToken(Uri stsUri, String targetServiceAudience, String authenticatorToken, HttpWebRequest request, TimeSpan timeout, EventTraceActivity eventTraceActivity, TimeSpan& expirationDuration)
--- End of inner exception stack trace ---
at Microsoft.Activities.Hosting.Security.OAuthS2SSecurityTokenServiceCredential.FetchAccessToken(Uri stsUri, String targetServiceAudience, String authenticatorToken, HttpWebRequest request, TimeSpan timeout, EventTraceActivity eventTraceActivity, TimeSpan& expirationDuration)
at Microsoft.Activities.Hosting.Security.OAuthS2SSecurityTokenServiceCredential.GetAccessTokenFromTokenService(OAuthS2SPrincipal client, OAuthS2SPrincipal targetServiceAudience, HttpWebRequest originalRequest, EventTraceActivity eventTraceActivity, TimeSpan& expirationDuration)
at Microsoft.Activities.Hosting.Security.OAuthS2SSecurityTokenServiceCredential.GetAuthorization(OAuthS2SAuthenticationChallenge[] bearerChallenges, HttpWebRequest request, EventTraceActivity eventTraceActivity)
at Microsoft.Activities.Hosting.Security.OAuthS2SAuthenticationModule.AuthenticateInternal(String challenge, WebRequest request, OAuthS2SCredential credential, EventTraceActivity eventTraceActivity)
at Microsoft.Activities.Hosting.Security.OAuthS2SAuthenticationModule.Authenticate(String challenge, WebRequest request, ICredentials credentials)
at System.Net.AuthenticationManagerDefault.Authenticate(String challenge, WebRequest request, ICredentials credentials)
at System.Net.AuthenticationState.AttemptAuthenticate(HttpWebRequest httpWebRequest, ICredentials authInfo)
at System.Net.HttpWebRequest.CheckResubmitForAuth()
at System.Net.HttpWebRequest.CheckResubmit(Exception& e, Boolean& disableUpload)
at System.Net.HttpWebRequest.DoSubmitRequestProcessing(Exception& exception)
at System.Net.HttpWebRequest.ProcessResponse()
at System.Net.HttpWebRequest.SetResponse(CoreResponseData coreResponseData)
--- End of inner exception stack trace ---
at Microsoft.Workflow.Common.AsyncResult.End[TAsyncResult](IAsyncResult result)
at Microsoft.Activities.Hosting.HostedHttpExtension.HttpRequestWorkItem.HttpRequestWorkItemAsyncResult.End(IAsyncResult result, Int32& responseCode)
at Microsoft.Activities.Hosting.HostedHttpExtension.HttpRequestWorkItem.OnEndComplete(ScheduledWorkItemContext context, IAsyncResult result)

 

Turns out that I had forgotten to enable the Workflows can use app permissions site feature:

So – If you’re not yet using Microsoft Flow and still need those SharePoint 2013 Workflows, remember to enable this site feature.

The Un-Deletable File

I’m not sure how I created it, but somehow I managed to create a folder called

'.

I can’t delete it from Windows Explorer, PowerShell, CMD,  [System.IO]::Delete(), or any other method I’ve attempted yet.  I also can’t delete the parent folder.

SDelete won’t work: https://technet.microsoft.com/en-us/sysinternals/sdelete.aspx

 

I’ve checked for and found no open handles on the file

I cannot move the folder (or it’s parent) to another location – It’s hard-fastened to my desktop.

My drive is BitLocker encrypted, so I can’t mount it on a Linux Device for deletion, and leveraging WinPE for BitLocker decryption would  likely subject me to the same file APIs .

 

InfoPath Notify on Specific Changed Fields

Scenario:

  • Notify specific users upon the change of any field in an infopath form excluding a limited number of fields.
  • InfoPath Full Trust Form
  • SharePoint designer Workflows

I’ve seen lots of people ask for this, with a lot of  the same general response:

it’s not really possible to maintain field-specific change history, or field-specific alerting

I think that Solution 2 below could address this.

Solution 1:

set up rules on every field in the form to set “NotifyParticipatns” to 1 on change.

Pros:

  • No code required

Cons:

  • Lots of rules and room for error

Solution 2:

Global event listener for all xml fields.  Note the XPath selector used for registering the Changed event handler:

"/my:myFields/*"

Conditional for which fields should be included / excluded from the alert.

Set the “NotifyParticipants” based on conditional result.


 public void InternalStartup()
 {
 EventManager.FormEvents.Loading += new LoadingEventHandler(FormEvents_Loading);
 EventManager.XmlEvents["/my:myFields/*"].Changed += new XmlChangedEventHandler(Form_Changed);
 }

<br data-mce-bogus="1">

public void Form_Changed(object sender, XmlEventArgs e)
 {
 // Write your code here to change the main data source.
 List&lt;string&gt; AlertableFields = new List&lt;string&gt;();
 string ModifiedField = e.Site.Name.ToString();
 AlertableFields.Add("my:Street");
 if( AlertableFields.Contains(ModifiedField))
 {
 Debug.WriteLine("Alerting on " + ModifiedField );
 }
 else{
 Debug.WriteLine("Not alerting on " + ModifiedField );
 }
 
 }

 

Pros:

  • Much cleaner solution

Cons:

  • Code.
  • Full Trust

 

ADFS Username Behavior

Problem

ADFS 4.0 on Windows Server 2016 tells users to log in with their full email address “someone@example.com.”  This generates many support requests, and complaints about too much typing.

Additionally, some extranet users may have email addresses not on the domain, and it’s unclear which email address they should supply.

This affects both the ADFS log in page, and the ADFS password change page.

Solution Methodology

ADFS Server 4.0 has PowerShell cmdlets to manage the content delivered to users during authentication requests: https://technet.microsoft.com/windows-server-docs/identity/ad-fs/operations/ad-fs-user-sign-in-customization

We’ll focus on the following

Get-AdfsWebTheme

and

Set-AdfsWebTheme

Of particular interest here is that we’re able to modify the JavaScript that runs on these pages.

Steps

Use PowerShell to manage custom ADFS Themes

  1. Export the Default ADFS Theme using this snippet:
     Export-ADFSWebTheme -Name "Default" -DirectoryPath c:\test
  2. Use your  favorite editor to open c:\test\script\onload.js
  3. Add the snippets from below (as desired) into onload.js
  4. Create a New ADFS Theme
     New-AdfsWebTheme -Name BetterDefault -SourceName c:\test 
    1. Set your new theme as the default (best for testing)
       Set-ADFSWebConfig -ActiveThemeName BetterDefault 
  5. Alternatively, you may update an existing theme with your code changes
    Set-AdfsWebTheme -TargetName "Default" -AdditionalFileResource @{Uri=“/adfs/portal/script/onload.js”;Path=“C:\theme\script\onload.js"}

Placeholder Text Solution

To update the “someone@example.com” placeholder on both the login and the password change ADFS pages, paste this code into your onload.js, and update your ADFS theme.

function UpdatePlaceholders() {
    var userName;
    if (typeof Login != 'undefined'){
        userName = document.getElementById(Login.userNameInput) 
    }
    if (typeof UpdatePassword != 'undefined'){
        userName = document.getElementById(UpdatePassword.userNameInput);
    }
    if (typeof userName != 'undefined'){
        userName.setAttribute("placeholder","Username");
    }
}

document.addEventListener("DOMContentLoaded", function(){
  // Handler when the DOM is fully loaded
  UpdatePlaceholders()
});

 

Formatting of the Username field

For single-domain organizations, it may be less than desirable to force users to enter the domain name as part of their username. To “fix” this requirement of entering usernames in a format of “domain\username” or “username@domain.com”, paste the following code into your onload.js.  Make sure to update your domain where appropriate.

Logon Username Format Solution

 


if (typeof Login != 'undefined'){
    Login.submitLoginRequest = function () { 
    var u = new InputUtil();
    var e = new LoginErrors();
    var userName = document.getElementById(Login.userNameInput);
    var password = document.getElementById(Login.passwordInput);

    if (userName.value && !userName.value.match('[@\\\\]')) 
    {
        var userNameValue = 'example.org\\' + userName.value;
        document.forms['loginForm'].UserName.value = userNameValue;
    }

    if (!userName.value) {
       u.setError(userName, e.userNameFormatError);
       return false;
    }


    if (!password.value) 
    {
        u.setError(password, e.passwordEmpty);
        return false;
    }
    document.forms['loginForm'].submit();
    return false;
};
}

Password Change Username Formatting Solution


if (typeof UpdatePassword != 'undefined'){
    UpdatePassword.submitPasswordChange = function () { 
    var u = new InputUtil();
    var e = new UpdErrors();

    var userName = document.getElementById(UpdatePassword.userNameInput);
    var oldPassword = document.getElementById(UpdatePassword.oldPasswordInput);
    var newPassword = document.getElementById(UpdatePassword.newPasswordInput);
    var confirmNewPassword = document.getElementById(UpdatePassword.confirmNewPasswordInput);

    if (userName.value && !userName.value.match('[@\\\\]')) 
    {
        var userNameValue = 'example.org\\' + userName.value;
        document.forms['updatePasswordForm'].UserName.value = userNameValue;
    }

    if (!userName.value) {
       u.setError(userName, e.userNameFormatError);
       return false;
    }

    if (!oldPassword.value) {
        u.setError(oldPassword, e.oldPasswordEmpty);
        return false;
    }

    if (oldPassword.value.length > maxPasswordLength) {
        u.setError(oldPassword, e.oldPasswordTooLong);
        return false;
    }

    if (!newPassword.value) {
        u.setError(newPassword, e.newPasswordEmpty);
        return false;
    }

    if (!confirmNewPassword.value) {
        u.setError(confirmNewPassword, e.confirmNewPasswordEmpty);
        return false;
    }

    if (newPassword.value.length > maxPasswordLength) {
        u.setError(newPassword, e.newPasswordTooLong);
        return false;
    }

    if (newPassword.value !== confirmNewPassword.value) {
        u.setError(confirmNewPassword, e.mismatchError);
        return false;
    }

    return true;
};
}

Thanks for reading!  If you have any questions, feel free to send me a tweet @crossan007.

SharePoint 2016 SMTP Authentication

Edit: It appears that this has been fixed in KB 3191880 :

SharePoint outbound email messages incorrectly try to authenticate to SMTP servers that support Generic Security Service Application Program Interface (GSSAPI), Kerberos, or NTLM authentication. This may prevent email messages from being sent. After you install this update, SharePoint sends email messages anonymously without authentication.


Recently I encountered an issue where SharePoint designer workflow’s emails not being delivered.

Additional inspection revealed that the messages in question were addressed to an Exchange Distribution group with “Permitted Senders.”  This designation meant that messages sent to this distribution group must be received from an authenticated sender (which SharePoint does not support by default: SHAREPOINT 2016 OUTBOUND SMTP FAILURES).

Old Solution

One solution I’ve used in the past is to setup Microsoft’s SMTP server on one of the SharePoint servers, and use that to relay (authenticated) messages to the Exchange server.   This has generally worked fine in the past, but  has always felt a little kludgey.

Seriously, Microsoft?  You’re recommending that we install IIS6 tools on a modern server?

The Problem

Anyway, the above solution breaks down with SharePoint 2016 in certain scenarios:  When sharing documents in SP2016, the “invitation” is sent as the user who initiated the invitation!!!

By default, Exchange only allows authenticated users to send as the account who’s credentials were supplied.

This presents a “Catch 22:”

  • Enable IIS6.0 SMTP relay to send Authenticated messages to Exchange and be able to relay to groups (and external domains)
    —————————-OR—————————————–
  • Configure SharePoint to send through an unauthenticated receive connector, and be allowed to send as any user, but not able to relay otuside the domain, or to groups which require authentication.

I went down a few different solution paths trying to solve this:

Failed Attempt 1: Grant Send-As Permission to SharePoint

Attempt to grant the  SharePoint SMTP service account (since I was already sending authenticated mail) “send-as” permissions on all mailboxes in the domain.

This just felt kludgey, and I was ultimately not able to get it to work.

I may have not waited the recommended 2 hours for the Mailbox Cache Idle Limit to expire:  https://technet.microsoft.com/en-us/library/aa996988(EXCHG.80).aspx

 

Successful Attempt: Configure Externally Secured Exchange Connector

The solution for me was to create a new “Externally Secured” Exchange Receive connector: https://technet.microsoft.com/en-us/library/mt668454(v=exchg.160).aspx

Essentially, this allows the hosts defined in the receive connector’s scope to deliver “unauthenticated” SMTP traffic as if it were authenticated.  

This fulfills my SharePoint requirements:

  •  To “send-as” on behalf of users in a document sharing scenario.
  • To send email as SharePoint to distribution groups which require the sender to be authenticated
  • To send email to users outside of my domain.

I hope this helps someone (even if it’s me in the future).

ADFS 4.0 on Server 2016 <-> Outlook Web App 2013

I recently enabled SAML authentication on Outlook Web App 2013, following the TechNet Documentation here: https://technet.microsoft.com/en-us/library/dn635116(v=exchg.150).aspx

It seemed to work fine; however, I would occasionally (about 1/8 attempts) receive an error message saying: “WrongAudienceUriOrBadSigningCert”

I had already added my ADFS token signing certificate to the Exchange server’s trusted root store as  suggested here:  https://flamingkeys.com/exchange-2013-with-ad-fs-login-fails-with-wrongaudienceuriorbadsigningcert/

The truly troubling thing was, that the issue could not be reproduced reliably.  It affected both internal and external devices (both the primary ADFS and the ADFS Web Application Proxy servers)

I watched a fiddler trace as I attempted to access OWA, and the only difference between successful and failed attempts was a  “/” at the end of the URL.

This can be observed in the POST body of the 302 to owa:


<saml:AudienceRestrictionCondition>
<saml:Audience>https://mail.corp.org/owa/</saml:Audience>
</saml:AudienceRestrictionCondition>

<saml:AudienceRestrictionCondition>
<saml:Audience>https://mail.corp.org/owa</saml:Audience>
</saml:AudienceRestrictionCondition>

This is the token issued to me by my ADFS4 Server!  It would seem that the tokens issued by the IdP do not contain a consistent Audience tag.

The TechNet documentation states very clearly that

The inclusion of the trailing slash / in the URL examples shown below is intentional. It’s important to ensure that both the AD FS relying party trusts and Exchange Audience URI’s are identical. This means the AD FS relying party trusts and Exchange Audience URI’s should both have or both emit the trailing slashes in their URLs. The examples in this section contain the trailing /’s after any url ending with “owa” ( /owa/) or “ecp” (/ecp/).

Ignoring this advise, I added all 4 urls to my Exchange farm configuration


$uris = @("https://mail.corp.org/owa/","https://mail.corp.org/ecp/","https://mail.corp.org/owa","https://mail.corp.org/ecp")

Set-OrganizationConfig -AdfsIssuer "https://adfs.corp.org/adfs/ls/" -AdfsAudienceUris $uris -AdfsSignCertificateThumbprint "<thhumb>"

Having 4 audience URIs resulted in a 100% success rate while attempting to open OWA from a successful ADFS authentication.

I hope this helps someone, as I couldn’t seem to find this issue anywhere else online.

Git Rebase

So, You’ve developed this great new feature and you’re ready to submit the code for inclusion into the project. You hit “pull request,” and patiently wait for feedback.

Then it happens.

Someone says “Can you merge this into [insert parent branch name here’]. You get a sinking feeling in your stomach, and say “oh no! now I have to make all of my changes over again from that branch.

Never fear, this is what rebasing is for!

In this case, you need to tell git to take the commits you’ve added, and play them back against a different branch.  The process goes something like this:


 git rebase --onto master myNewFeatureBranch~1 myNewFeatureBranch

If all goes well, you’ll end up in the same branch, all of your changes will be intact, and the result of (diff to master) will be only your changes!!!

Also Collapse commits: http://stackoverflow.com/questions/6884022/collapsing-a-group-of-commits-into-one-on-git

 

SharePoint 2016 Outbound SMTP Failures

Recently I was configuring a SharePoint 2016 farm, and encountered some peculiar issues with outbound email.

SharePoint 2016 is the first version of SharePoint to include built-in support for TLS. In any previous version of SharePoint, TLS requirements were fulfilled by setting up a SMTP relay capable of authenticating to the desired target SMTP server.

Interestingly, It seems that SharePoint 2016 also responds to SMTP authentication challenges despite not having an explicit configuration option in Central Administration for which credentials to use for SMTP.

The issue I recently experienced is as follows:

  • List / Library “initial” alert subscription messages are delivered to the appropriate address
  • Actual alerts from a list / library are not delivered
  • Workflow Task emails are not delivered

Digging into the ULS logs of the SharePoint server, I noticed the following:

  • Messages send by w3wp (running under the web app pool service account) were delivered
  • Messages sent by OWSTIMER (running under the farm account) were not delivered.  The timer job in question is “job-immediate-alerts.”

So, despite having outbound email configured in Central Administration, it seems that SharePoint is not treating different classes of outbound email equally.

I tried many of the “well known fixes” to no avail:

  • Re-starting the server
  • Re-starting the timer service
  • Manually starting the job-immediate-alerts timer job with PowerShell
  • Altering the alerts properties of the site with stsadm

I finally broke out WireShark on my SharePoint server to observe the SMTP traffic.  What I found was interesting:

  • Messages sent by w3wp.exe had these characteristics:
    • SharePoint sends the message immediately upon request from the browser to subscribe to alerts on a library
    • SharePoint opens a SMTP session to the configured server
    • The Exchange 2013 server responds with an SMTP ntlm authentication challenge
    • The SharePoint server provides the credentials of the web app service account!
    • Exchange returns with smtp 5.7.1 client was not authenticated. 
    • SharePoint ignores the 5.7.1 error message, and delivers the message anyway
  • Message sent by OWSTIMER.exe had these characteristics:
    • SharePoint attempts to send the message with each execution of the job-immediate-alerts timer job.
    • SharePoint opens a SMTP session to the configured server
    • The Exchange 2013 server responds with an SMTP ntlm authentication challenge
    • The SharePoint server provides the credentials of the farm service account!
    • Exchange returns with smtp 5.7.1 client was not authenticated. 
    • SharePoint stops attempting to deliver the message because of the error!

In both of these scenarios, neither the farm service account, nor the web app service account are configured with Exchange mailboxes, so the authentication fails.

The receive connector in Exchange is configured to allow TLS, Exchange Authentication, and Anonymous authentication.

The unexpected behavior is this: SharePoint reacts to an SMTP 5.7.1. unauthenticated message differently depending on the context from which the SMTP session was initiated.  SMTP sessions initiated directly in the web app context succeed, but SMTP sessions initiated from timer jobs fail.

My temporary solution was to create a separate receive connector in Exchange on a separate port scoped so to only the SharePoint server’s IP that allows only anonymous authentication (it seems that by having Exchange Authentication checked, SharePoint fails).  This causes the Exchange server to never prompt the SharePoint server for STMP authentication, and therefore messages are delivered.

I’ll update this post as I discover more.

Display Approval Tasks on InfoPath Form

I’be been working through building InfoPath forms to streamline the approval process of some internal documents, and one of the project requirements is to display the date / time as well as comments of each person who approves a document.

I built a SharePoint Designer workflow which first computes the approval routing (which varies between 8 and 10 approvers depending on the value of some fields),  then collects the approvals via the “Start Approval Process” task, and then emails up to 10 SharePoint groups based on a different set of criteria on each document.

SharePoint Designer Workflows store these “Assigned Tasks” in a Task List, which the developer is able to specify.  Each Task in the Task List contains a HIDDEN COLUMN called WorkflowItemId which associates the Task with the Item against which the workflow is running.   This column is a pesky little bugger for reasons explained below.

There is a blog post which describes one method for displaying all approvals tasks on the actual InfoPath form which goes roughly as follows:

  1. Create a new custom list containing all of the columns you need to reference
  2. Edit the “Behavior of a Single Task” for the Approval Process in question so that if the outcome is approved, add a new item to the custom list
  3. Add a Data Connection on the InfoPath form to pull data from the new custom list and display it on the form.

I didn’t want to go through the hassle of creating a separate list for each workflow I’m running, just to store data that’s already being stored in the associated Task List.

So, the big question: Why don’t you just add the Task List as an InfoPath Datasource and call it a day?

Well, the answer to that question may infuriate you: you are unable to filter the list according to the ID of the item in question because the attribute that stores the item id (WorkflowItemId) is forcibly hidden! 

  • InfoPath does not provide WorkflowItemId as an option in the Data Connection query path.
  • CSOM CAML queries error out when you attempt to use WorkflowItemId as a query field, so the SOAP / REST Data Connections in InfoPath also fail.

Other than the solution above, there are really only two other options:

I went the second route, and created such a web service, which is available here: https://github.com/bciu22/ApprovalTaskListService.

The result is that you can add an InfoPath Data Connection that looks something like this:

So that you can have a repeating table on your form with all approvals that looks something like this: