Tag Archives: Microsoft

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 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:

 

Windows 7 Update Pain!

For those unfortunate enough to still be deploying Windows 7, I implore you to ensure your workstations have at least one of  the following updates to save yourself much pain:

These updates address a situation where the Windows Update client causes the machine to grind to a halt. This can be especially troublesome if you’re beginning to manage your environment with SCCM (even if you’re only deploying SCEP updates) as SCCM’s updating mechanism relies upon the Windows Update client.

So, before you deploy any updates to Windows 7 clients, ensure the machines have the above KB’s installed!

For more information, follow this TechNet blog post: https://social.technet.microsoft.com/Forums/windows/en-US/4a782e40-bbd8-40b7-869d-68e3dfd1a5b4/windows-update-scan-high-memory-usage?forum=w7itproperf&prof=required

SCCM 1511 Driver Migration Fails

While migrating drivers from SCCM 2012 to SCCM 1511, the migration can fail with the following error message:

Couldn't find the specified instance SMS_CategoryInstance.CategoryInstance_UniqueID='DriverCategories:

This occurs given the following scenario:

  1. A driver is added to SCCM
  2. A category is assigned to the driver
  3. The category is deleted from the driver
  4. The migration job attempts to migrate the driver to a new SCCM environment

Consider the following error message:

Couldn't find the specified instance SMS_CategoryInstance.CategoryInstance_UniqueID='DriverCategories:78266da1-ebac-4c12-b2c8-89451383b03e

An In-depth analysis shows that the driver category does not appear in the WMI Class SMS_CategoryInstance:

Get-WmiObject -Namespace $Namespace -Class SMS_CategoryInstance -Filter "CategoryInstance_UniqueID = 'DriverCategories:78266da1-ebac-4c12-b2c8-89451383b03e'"

Returns Null, but a SQL query indicates that the Category Instance still exists:
SELECT * FROM CI_CategoryInstances

WHERE CategoryInstance_UniqueID
LIKE ‘%78266da1-ebac-4c12-b2c8-89451383b03e%’

 

Returns the following:

CategoryInstanceID CategoryInstance_UniqueID CategoryTypeName DateLastModified SourceSite ParentCategoryInstanceID IsDeleted rowversion
16777602 DriverCategories:78266da1-ebac-4c12-b2c8-89451383b03e DriverCategories 2015-07-27 11:52:33.000 CM1 NULL 1 0x000000000A36B34D

Take note of the “IsDeleted” column – set to True

So, at this point, WMI reports that this category no longer exists; however the category is still in the SQL Database, though this is not the root of the problem.

Now consider the WMI Class SMS_CategoryInstanceMembership:
This class contains a correlation of Objects to CategoryIDs.  This is not limited to Driver <-> Driver Category correlation.

The true bug seems to be that when a driver category is removed, the entry for that driver category in the  SMS_CategoryInstanceMembership class is not removed.  In order to fix this, and have a successful migration, we need to manually remove the CategoryInstanceMembership object for any drivers that were assigned a now deleted category.

to Fix:

  1. Fire Up SQL Server Management Studio
  2. Create a New Query, and select your Site Database
  3. For each failing driver entry (as seen in “C:\Program Files\Microsoft Configuration Manager\Logs\migmctrl.log”),
    1. Run the following SQL Query:
      SELECT * FROM CI_CategoryInstances
      WHERE CategoryInstance_UniqueID
      LIKE '%c6e9d9e3-1371-46ba-b7f1-bb46c5b6bc06%'
    2. Note the CategoryInstanceID  (should be like 16777601)
    3. On your SCCM Server, from an administrative PowerShell run the following code to remove the association, substituting the CategoryInstanceID you discovered in step 3-2:
      Get-WmiObject -Namespace $Namespace -Class SMS_CategoryInstanceMembership -Filter "CategoryInstanceID = '16777601'" | Foreach-Object {
      Remove-WmiObject -InputObject $_
      }
  4. Repeat the above section for each unique CategoryInstance_UniqueID listed in the “C:\Program Files\Microsoft Configuration Manager\Logs\migmctrl.log” file.
  5. When complete, retry the migration, and examine for any additional missing CategoryInstance_UniqueID errors.

References (In order of usefulness):

  1. http://cm12sdk.net/?p=981
  2. https://www.reddit.com/r/SCCM/comments/34b18i/delete_multiple_driver_admin_categories/
  3. https://technet.microsoft.com/en-us/library/hh849820.aspx
  4. https://technet.microsoft.com/en-us/library/dn151092(v=sc.20).aspx

SQL AlwaysOn Availability Group User Accounts

When creating SQL 2014 AlwaysOn Availability Groups, careful attention is required when provisioning the logins on each member server.

While the databases may contain user accounts for the appropriate members, the cluster member servers may not contain login information for said users. This can result in a seemingly “happy” fail over cluster (according to the dashboard in SQL Server Management Studio), but upon fail over, much pain will occur.

From a 10,000 foot view, the Logins on each server need to have the same SID, Username, and Password.

More detail (along with a script to rectify any “on-noes” that may have occurred in your environment): https://support.microsoft.com/en-us/kb/918992

This page contains some best practices for avoiding the described issue: https://aalamrangi.wordpress.com/2015/02/09/avoid-orphan-users-in-alwayson/

Unprotect an Excel Worksheet

C/O: https://uknowit.uwgb.edu/page.php?id=28850

  1. Press ALT+F11
  2. Double Click the worksheet in question
  3. Paste the Following Code:
    
    Sub PasswordBreaker()
    
    'Breaks worksheet password protection.
    Dim i As Integer, j As Integer, k As Integer
    Dim l As Integer, m As Integer, n As Integer
    Dim i1 As Integer, i2 As Integer, i3 As Integer
    Dim i4 As Integer, i5 As Integer, i6 As Integer
    On Error Resume Next
    For i = 65 To 66: For j = 65 To 66: For k = 65 To 66
    For l = 65 To 66: For m = 65 To 66: For i1 = 65 To 66
    For i2 = 65 To 66: For i3 = 65 To 66: For i4 = 65 To 66
    For i5 = 65 To 66: For i6 = 65 To 66: For n = 32 To 126
    ActiveSheet.Unprotect Chr(i) &#038; Chr(j) &#038; Chr(k) &#038; _
    Chr(l) &#038; Chr(m) &#038; Chr(i1) &#038; Chr(i2) &#038; Chr(i3) &#038; _
    Chr(i4) &#038; Chr(i5) &#038; Chr(i6) &#038; Chr(n)
    If ActiveSheet.ProtectContents = False Then
    MsgBox "One usable password is " &#038; Chr(i) &#038; Chr(j) &#038; _
    Chr(k) &#038; Chr(l) &#038; Chr(m) &#038; Chr(i1) &#038; Chr(i2) &#038; _
    Chr(i3) &#038; Chr(i4) &#038; Chr(i5) &#038; Chr(i6) &#038; Chr(n)
    Exit Sub
    End If
    Next: Next: Next: Next: Next: Next
    Next: Next: Next: Next: Next: Next
    End Sub
    
  4. Press the Run button
  5. The password to unlock the form is displayed, and the sheet is no longer password protected

Pushing Calendar Events with the EWS API

We’ve had a need to populate users’ calendars with data from an internal FileMaker Database, so I dug around in the EWS API, and came up with a script that uses the FileMaker ODBC Connection, and the EWS API to accomplish the task:

First things first, we need to install the EWS Managed API on the machine that will run the script.

After the EWS Managed API is installed, we need to reference it in our PowerShell script:

Add-Type -Path “C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll”

Next, we need to set up a System.Net.NetworkCredential object for the account we’ll use to push these events.  This account must have at least modify permission on the target users’ calendar.

$Credentials = new-object system.net.NetworkCredential(“CalendarAccessAccount”,”SuperStrongPa$$w0Rd!”,”litware”)

Next, we need to Create anMicrosoft.Exchange.WebServices.Data.ExchangeService object:

$version = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService($version)

We don’t want to use the default credentials, Instead we want to authenticate using the service account specified earlier:

$service.UseDefaultCredentials = $false
$service.Credentials=$Credentials

And, presuming AutoDiscover is set up correctly in our domain, we want to let EWS figure out the server address, port, etc:

$service.AutodiscoverUrl(“TargetMailbox@litware.com”)

Next, we need to reference the user’s calendar (it’s really just a folder as far as the API is concerned):

$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, “TargetMailbox@litware.com”)

And finally, we build the appointment object:

$Appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $Service
$appointment.Subject = “Test111”
$appointment.Body = “Test111”
$appointment.Start = $(Get-Date).AddHours(6)
$appointment.End =$(Get-Date).AddHours(9)

Don’t forget to save it:

$appointment.Save($folderid)

 

All in all, we can wrap this up as a function:

Function CreateAppointment($User,$Credentials)
{
$mailboxName=$User
$version = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService($version)
$service.UseDefaultCredentials = $false
$service.Credentials=$Credentials
$service.AutodiscoverUrl($mailboxName)

$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $mailboxName)

$Appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $Service
$appointment.Subject = “Test Subject”
$appointment.Body = “Test Body”
$appointment.Start = $(Get-Date).AddHours(6)
$appointment.End =$(Get-Date).AddHours(9)

$appointment.Save($folderid)

}

Now we can call the function from, say, with a loop so as to iterate through each user in a CSV:

$users = Import-CSV “Users.csv”

$Credentials = new-object system.net.NetworkCredential(“CalendarAccessAccount”,”SuperStrongPa$$w0Rd!”,”litware”)

Foreach ($User in $Users)

{

CreateAppointment $User $Credentials

}

 

More to come later on the FileMaker ODBC Connection…

FIM Portal No Access for FIM Admin Account

Today’s adventure with Forefront Identity Manager started when I was unable to log into the FIM portal.  Some digging revealed that the accountName attribute for my admin user had been set to null (probably from too much tinkering with sync rules).

I realized that the accountName was probably the issue by two indicators: there was no account name attribute for the FIM Admin object in the FIM Synchronization Service Manager application, and because the query below referencing the ObjectValueString table lacked some attributes. The change-fimadmin.ps1 script helped me determine these SQL sanity check queries.

I had already eliminated the usual suspects for not being able to access the portal (ObjectSID, MPRs, etc), so this stumped me for a little while

Anyway, I needed a way to get back in the portal (and I didn’t want to re-install), so I came up with this script that uses the FIM PowerShell modules to set the accountName attribute of the FIM Admin user (identified by the well-known admin user GUID).

I used the script on How to Use PowerShell to Set the Required Attributes for the FIM Portal Access as a starting point, modifying it to set only the accountName attribute.

$adminAccountName=”accountNameHere”

If(@(get-pssnapin | where-object {$_.Name -eq “FIMAutomation”} ).count -eq 0) {add-pssnapin FIMAutomation}

Function SetAttribute
{
PARAM($CurObject, $AttributeName, $AttributeValue)
END
{
$ImportChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
$ImportChange.Operation = 1
$ImportChange.AttributeName = $AttributeName
$ImportChange.AttributeValue = $AttributeValue
$ImportChange.FullyResolved = 1
$ImportChange.Locale = “Invariant”
If ($CurObject.Changes -eq $null) {$CurObject.Changes = (,$ImportChange)}
Else {$CurObject.Changes += $ImportChange}
}
}
$curObject= export-fimconfig -uri $URI –onlyBaseResources -customconfig (“/Person[ObjectID='{7fb2b853-24f0-4498-9534-4e10589723c4}’]”)

$ImportObject = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject

$ImportObject.ObjectType = $curObject.ResourceManagementObject.ObjectType
$ImportObject.TargetObjectIdentifier = $CurObject.ResourceManagementObject.ObjectIdentifier
$ImportObject.SourceObjectIdentifier = $CurObject.ResourceManagementObject.ObjectIdentifier
$ImportObject.State = 1

SetAttribute -CurObject $ImportObject -AttributeName “AccountName” -AttributeValue $adminAccountName
$ImportObject | Import-FIMConfig -uri $URI -ErrorVariable Err -ErrorAction SilentlyContinue

 

After running this script, you should be able to log into the FIM portal again.

Helpful places to look also include the FIMService database.  Particularly the ObjectValueString and UserSecurityIdentifiers Tables.

 

The following query represents the values for the FIM Admin User, and should yield 7 rows(Attribute Keys 1,66,68,70,117,125,132)

SELECT TOP 1000 [AttributeID]
,[ObjectKey]
,[ObjectTypeKey]
,[AttributeKey]
,[SequenceID]
,[LocaleKey]
,[ValueString]
,[Multivalued]
FROM [FIMService].[fim].[ObjectValueString]

where ObjectKey =2340

The following query represents the SID, in HEX form, of the FIM Admin User, and should yield 1 row:

SELECT TOP 1000 [UserObjectKey]
,[SecurityIdentifier]
FROM [FIMService].[fim].[UserSecurityIdentifiers]
where UserObjectKey =2340