14

I have a very small test application in which I'm trying to install a Windows Service and create a LocalDB database during the install process, then connect to that LocalDB database when the Windows Service runs.

I am running into huge problems connecting to a LocalDB instance from my Windows Service.

My installation process is exactly like this:

  1. Execute an installer .msi file which runs the msiexec process as the NT AUTHORITY\SYSTEM account.

  2. Run a custom action to execute SqlLocalDB.exe with the following commands:

    • sqllocaldb.exe create MYINSTANCE
    • sqllocaldb.exe share MYINSTANCE MYINSTANCESHARE
    • sqllocaldb.exe start MYINSTANCE
  3. Run a custom C# action using ADO.NET (System.Data.SqlConnection) to perform the following actions:

    • Connect to the following connection string, Data Source=(localdb)\MYINSTANCE; Integrated Security=true
    • CREATE DATABASE TestDB
    • USE TestDB
    • CREATE TABLE ...
  4. Start the Windows Service before the installer finishes.

  5. The Windows Service is installed to the LocalSystem account and so also runs as the NT AUTHORITY\SYSTEM user account.

  6. The service attempts to connect using the same connection string used above.

I am consistently getting the following error when trying to open the connection to the above connection string from within the Windows Service:

System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 50 - Local Database Runtime error occurred. The specified LocalDB instance does not exist.

This is frustrating because both the msi installer custom action and the Windows Service are running under the same Windows user account (I checked, they're both NT AUTHORITY\System). So why the first works and the second does not is beyond me.

I have tried changing the connection strings used in the custom action and the Windows Service to use the share name (localdb)\.\MYINSTANCESHARE and I get the exact same error from the Windows Service.

I have tried changing the user account that the Windows Service logs on as to my Windows user account, which does work as long as I first run a command to add it to the SQL server logins for that instance.

I've also tried running a console application and connecting to the share name connection string and that works as well.

I've also tried connecting to the share name from SQL Server Management Studio and that works as well.

However none of these methods really solve my problem. I need a Windows Service because it starts up as soon as the computer starts up (even if no user logs on) and starts up no matter which user account is logged in.

How does a Windows Service connect to a LocalDB private instance?

I am using SQL Server 2014 Express LocalDB.

Trevor Elliott
  • 11,292
  • 11
  • 63
  • 102
  • I suspect Windows is now creating "virtual accounts" for all services running as System, so they cannot communicate with each other. I might be wrong, I vaguely remember reading something to this effect somewhere, a while back. But can't check it now. Hope that helps. – Krzysztof Kozielczyk Oct 28 '14 at 16:26
  • For testing purposes I tried having the service run as a non-system account but not the account which was used during the creation of the LocalDB instance. I got the same error. So I'm not sure that the problem is that it's a system account I'm using, the problem is either the fact that the SqlLocalDB.exe utility is being run as the system account to create the instance, or that the sharing between accounts just isn't working at all. – Trevor Elliott Oct 31 '14 at 16:11
  • After the installer finishes, where is the instance created physically? It should be in a location similar to: **C:\Users\{some login}\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances** . Keep in mind that LocalDB is "user mode" so it has to be in the context of a user and I am guessing that the virtual accounts don't qualify for owning the instance/db, but should still be fine for connecting. – Solomon Rutzky Oct 31 '14 at 17:23
  • Also, what version of .Net are you using for the Windows Service? Is the local system account considered an administrative account? And are you using either double-back-slashes in the connection string, or if not, then prefixing the string with an "@"? – Solomon Rutzky Oct 31 '14 at 17:45
  • @srutzky The instance is supposed to be created in `C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances` which is the proper AppData folder for the SYSTEM account. However, I'm seeing that while the .mdf and .ldf files are being created in `C:\Windows\System32\config\systemprofile`, the actual instance folder is being created in the `C:\Users\{current user}\...` folder for the user which is active. – Trevor Elliott Nov 03 '14 at 00:42
  • So it's as if LocalDB isn't actually created the instance with the SYSTEM account even though my logging reports that it is being run as the `NT AUTHORITY\SYSTEM` user. The Windows Service is running under .NET 4.5.1 and yes I am properly escaping my backslashes in the connection strings. After all the same connection string works when I change the user account the service is running under. – Trevor Elliott Nov 03 '14 at 00:43
  • Have you tried sqllocaldb.exe share "NT AUTHORITY\SYSTEM" MYINSTANCE MYINSTANCESHARE – Steve Ford Nov 05 '14 at 14:14

5 Answers5

14

Picking up from the comments on the question, here are some areas to look at. Some of these have already been answered in those comments, but I am documenting here for others in case the info might be helpful.


  • What version of .Net is being used? Here it is 4.5.1 (good) but earlier versions could not handle the preferred connection string (i.e. @"(localdb)\InstanceName"). The following quote is taken from the link noted above:

    If your application uses a version of .NET before 4.0.2 you must connect directly to the named pipe of the LocalDB.

    And according to the MSDN page for SqlConnection.ConnectionString:

    Beginning in .NET Framework 4.5, you can also connect to a LocalDB database as follows:

    server=(localdb)\\myInstance

  • Paths:

    • Instances: C:\Users{Windows Login}\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances

    • Databases:

      • Created via SSMS or direct connection: C:\Users{Windows Login}\Documents or C:\Users{Windows Login}
      • Created via Visual Studio: C:\Users{Windows Login}\AppData\Local\Microsoft\VisualStudio\SSDT


  • Initial Problem
    • Symptoms:
      • Database files (.mdf and .ldf) created in the expected location:
        C:\Windows\System32\config\systemprofile
      • Instance files created in an unexpected location:
        C:\Users\{current user}\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances
    • Cause (note taken from "SqlLocalDB Utility" MSDN page that is linked above; emphasis mine):

      Operations other than start can only be performed on an instance belonging to currently logged in user.


  • Things to try:

    • Connection string that specifies the database (though maybe a long-shot if the error is regarding not being able to connect to the instance):
    • "Server=(LocalDB)\MYINSTANCE; Integrated Security=true ;AttachDbFileName=C:\Windows\System32\config\systemprofile\TestDB.mdf"
    • "Server=(LocalDB)\.\MYINSTANCESHARE; Integrated Security=true ;AttachDbFileName=C:\Windows\System32\config\systemprofile\TestDB.mdf"

    • Is the service running? Run the following from a Command Prompt:
      TASKLIST /FI "IMAGENAME eq sqlservr.exe"
      It should probably be listed under "Console" for the "Session Name" column

    • Run the following from a Command Prompt:
      sqllocaldb.exe info MYINSTANCE
      And verify that the value for "Owner" is correct. Is the value for "Shared name" what it should be? If not, the documentation states:

      Only an administrator on the computer can create a shared instance of LocalDB

    • As part of the setup, add the NT AUTHORITY\System account as a Login to the system, which is required if this account is not showing as the "Owner" of the instance:
      CREATE LOGIN [NT AUTHORITY\System] FROM WINDOWS; ALTER SERVER ROLE [sysadmin] ADD MEMBER [NT AUTHORITY\System];

    • Check the following file for clues / details:
      C:\Users{Windows Login}\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MYINSTANCE\error.log

    • In the end you might need to create an actual account to create and own the Instance and Database, as well as run your service. LocalDB really is meant to be user-mode, and is there any downside to having your service have its own login? And you probably wouldn't need to share the instance at that point.

      And in fact, as noted by Microsoft on the SQL Server YYYY Express LocalDB MSDN page:

      An instance of LocalDB owned by the built-in accounts such as NT AUTHORITY\SYSTEM can have manageability issues due to windows file system redirection; Instead use a normal windows account as the owner.

UPDATE (2015-08-21)

Based on feedback from the O.P. that using a regular User account can be problematic in certain environments, AND keeping in mind the original issue of the LocalDB instance being created in the %LOCALAPPDATA% folder for the user running the installer (and not the %LOCALAPPDATA% folder for NT AUTHORITY\System ), I found a solution that seems to keep with the intent of easy installation (no user to create) and should not require needing extra code to load the SYSTEM profile.

Try using one of the two built-in accounts that is not the LocalSystem account (which does not maintain its own registry info. Use either:

Both have their profile folders in: C:\Windows\ServiceProfiles

While I have not been able to test via an installer, I did test a service logging on as NT AUTHORITY\NetworkService by setting my SQL Server Express 2014 instance to log on as this account, and restarted the SQL Server service. I then ran the following:

EXEC xp_cmdshell 'sqllocaldb c MyTestInstance -s';

and it created the instance in: C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances

I then ran the following:

EXEC xp_cmdshell N'SQLCMD -S (localdb)\MyTestInstance -E -Q "CREATE DATABASE [MyTestDB];"';

and it had created the database in: C:\Windows\ServiceProfiles\NetworkService

Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • 1
    Sorry for the late response. I had to start another bounty and will award it when it lets me. In the end your final suggestion was what worked for me. I had to modify my MSI installer to create a user account and assign it to the administrators group, then install the service and have it run as the user account I created, then I had to run all of my MSI custom actions in such a way that they would execute SqlCmd and SqlLocalDb as the user account I created (using Process.Start with the username and password supplied). – Trevor Elliott Nov 10 '14 at 00:55
  • @TrevorElliott : Thanks for following up :). Just out of curiosity, did you try Rolo's suggestion? Either way, I think Microsoft made a clear distinction (in a blog or two) that LocalDB was user-mode and if you need a back-end service then you should use the full SQL Server Express Edition. Regarding the bounty: a) thanks for doing that, and b) I noticed that CJBS also suggested creating a new account so I would be willing to split the reward if you are able to do that. – Solomon Rutzky Nov 10 '14 at 04:33
  • Rolo's answer didn't help. Using the Impersonation attribute allows you to either execute a command as the Local System account or the current user. We've determined LocalDB does not work properly with the System account. And the problem with the current user account is that the application is supposed to install for all users and I also wouldn't know the password to the current user account to ensure the service starts with it. – Trevor Elliott Nov 10 '14 at 16:22
  • This is absolutely correct Trevor, my answer should theoretically work so I decided to implement this myself and I can see that everything is 100% correctly created under Local System account, but when the windows service tries to connect you receive "A network-related or instance-specific error...". Then I agree, the System account doesn't play well with LocalDB. The correct approach is, as proposed and accepted here, create a user account specific for the service. – Rolo Nov 11 '14 at 02:38
  • @TrevorElliott : I just added a 4th bullet to the "info" section at the top of my answer. It is a blog post from the SQL Server Express team. It has some good info as well as a Q&A section where one of the question was specific to running from a service. Also, not sure if you saw Rolo's comment above since you weren't tagged in it. Nothing really new in either my addition or his comment, but it does help paint a clearer picture that the path you chose is probably the only one that would work (as opposed to wondering if there is still another way). – Solomon Rutzky Nov 11 '14 at 17:06
  • @TrevorElliott : to be fair, your new solution of loading the system profile was mentioned in my answer. Given that I did not provide code or details for that I do understand (and agree with) moving the accepted answer. However, I am not sure that removing the upvote on my answer was warranted, especially given the official Microsoft recommendation to use a regular account instead of a built-in account (which I just added to the end of my answer). Either way, I'm glad you got your preferred solution. – Solomon Rutzky Aug 06 '15 at 17:28
  • @srutzky The crux of my question was getting LocalDB to work with a built-in service account. Your answer led me to the conclusion that this was not a good way to do it, and I should create a user account for the purpose of logging on to the service. So far in my experience of deploying with the "recommended" way of creating your own user account, I have run into a plethora of issues which hundreds of customers are affected by. Creating your own user account is NOT a good solution. MS itself does not do this, they cheat and create built-in user accounts such as IIS_IUSR. – Trevor Elliott Aug 06 '15 at 19:03
  • MS knows that the user account approach has issues, which is why in Windows 7 or later there is a new API to create "Managed Service Accounts" which are designed to reduce the issues that you have with traditional user accounts. They're designed for exactly this scenario. But unfortunately since I need to support Vista/Server 2008 I could not use them. – Trevor Elliott Aug 06 '15 at 19:10
  • @TrevorElliott So what are the issues that you have run into when deploying with a regular account? As far as I know, the `IUSR_***` and `ASPNET` accounts are not built-in system accounts, not like `NT AUTHORITY\System` and the 1 or 2 others are. – Solomon Rutzky Aug 06 '15 at 21:42
  • According to MS they are built-in. They do not have a user profile (eg. C:\Users\IIS_IUSR). They do not have passwords. – Trevor Elliott Aug 06 '15 at 22:05
  • The issues with user accounts are varied, but mainly to do with domain environments. In some default Windows Server installations non-Administrator accounts cannot even logon locally (eg. run a process as that user account). Or logon as a service. In many domain-joined desktops when you try to add the Logon as a service right it will fail because the policy is being managed at the domain controller level. Password complexity and age requirements can vary, and setting the "Password never expires" flag doesn't always override these policies. – Trevor Elliott Aug 06 '15 at 22:07
  • @TrevorElliott I did some research (http://www.iis.net/learn/get-started/planning-for-security/understanding-built-in-user-and-group-accounts-in-iis) and found that we were each referring to different accounts: `IUSR_{MachineName}` and `ASPNET` are Local User accounts; `IUSR` and `IIS_IUSRS` are built-in. I also better understand your situation now. I don't think many people could have foreseen those issues, though I am sorry that you had to deal with that frustration. However, I tested with a non-`LocalSystem` built-in account and had positive results. I updated my answer with that info. – Solomon Rutzky Aug 21 '15 at 21:31
3

I was able to solve similar issue in our WiX installer recently. We have a Windows service, running under SYSTEM account, and an installer, where LocalDB-based storage is one of the options for database configuration. For some time (a couple of years actually) product upgrades and service worked quite fine, with no issues related to LocalDB. We are using default v11.0 instance, which is created in SYSTEM profile in C:\Windows\System32\config tree, and a database specified via AttachDbFileName, created in ALLUSERSPROFILE tree. DB provider is configured to use Windows authentication. We also have a custom action in installer, scheduled as deferred/non-impersonate, which runs DB schema updates.

All this worked fine until recently. After another bunch of DB updates, our new release started to fail after having upgraded over the former - service was unable to start, reporting infamous "A network-related or instance-specific error occurred while establishing a connection to SQL Server" (error 50) fault.

When investigating this issue, it became apparent that the problem is in a way WiX runs custom actions. Although non-impersonated CA-s run under SYSTEM account, the registry profile and environment remain that of current user (I suspect WiX loads these voluntary when attaching to user's session). This leads to incorrect path being expanded from the LOCALAPPDATA variable - the service receives SYSTEM profile one, but the schema update CA works with the user's one.

So here are two possible solutions. The first one is simple, but too intrusive to user's system - with cmd.exe started via psexec, recreate broken instance under the SYSTEM account. This was not an option for us as the user may have other databases created in v11.0 instance, which is public. The second option assumed lots of refactoring, but wouldn't hurt anything. Here is what to do to run DB schema updates properly with LocalDB in WiX CA:

  1. Configure your CA as deferred/non-impersonate (should run under SYSTEM account);
  2. Fix environment to point to SYSTEM profile paths:

    var systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
    Environment.SetEnvironmentVariable("USERPROFILE", String.Format(@"{0}\System32\config\systemprofile", systemRoot));
    Environment.SetEnvironmentVariable("APPDATA", String.Format(@"{0}\System32\config\systemprofile\AppData\Roaming", systemRoot));
    Environment.SetEnvironmentVariable("LOCALAPPDATA", String.Format(@"{0}\System32\config\systemprofile\AppData\Local", systemRoot));
    Environment.SetEnvironmentVariable("HOMEPATH", String.Empty);
    Environment.SetEnvironmentVariable("USERNAME", Environment.UserName);
    
  3. Load SYSTEM account profile. I used LogonUser/LoadUserProfile native API methods, as following:

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
    
    [StructLayout(LayoutKind.Sequential)]
    struct PROFILEINFO
    {
        public int dwSize; 
        public int dwFlags;
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpUserName; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpProfilePath; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpDefaultPath; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpServerName; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpPolicyPath; 
        public IntPtr hProfile; 
    }
    
    [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
    
    var hToken = IntPtr.Zero;
    var hProfile = IntPtr.Zero;
    bool result = LogonUser("SYSTEM", "NT AUTHORITY", String.Empty, 3 /* LOGON32_LOGON_SERVICE */, 0 /* LOGON32_PROVIDER_DEFAULT */, ref token);
    if (result)
    {
        var profileInfo = new PROFILEINFO();
        profileInfo.dwSize = Marshal.SizeOf(profileInfo);
        profileInfo.lpUserName = @"NT AUTHORITY\SYSTEM";
        if (LoadUserProfile(token, ref profileInfo))
            hProfile = profileInfo.hProfile;
    }
    

    Wrap this in an IDisposable class, and use with a using statement to build a context.

  4. The most important - refactor your code to perform necessary DB updates in a child process. This could be a simple exe-wrapper over your installer DLL, or stand-alone utility, if your already have one.

P.S. All these difficulties could be avoided, if only Microsoft let uses choose where to create LocalDB instances, via command line option. Like Postgres' initdb/pg_ctl utilities have, for example.

denis.gz
  • 83
  • 4
  • Very insightful answer. We ended up creating a user account for the purpose of running our Windows service to use LocalDB. That has its own issues associated with it. – Trevor Elliott May 26 '15 at 20:21
  • One clarification with your answer: the code in step 2 and 3, should this run in a custom action? Should this code execute before calling a child process? Or can it run in the same process which then interfaces with LocalDb? In my testing so far LogonUser with the System account always returns 1326 (Invalid username or password). – Trevor Elliott Aug 04 '15 at 16:52
  • Yes, all the steps should run in a custom action. You should set up environment and load SYSTEM user profile before calling the child process. I have fixed the code which passed `null' for password when calling LogonUser, it should be empty string instead. Then, in a child process, connect to LocalDB and run your updates. – denis.gz Aug 05 '15 at 18:07
  • I got it working. :) I didn't need a child process, I was able to connect to LocalDB directly from the custom action after setting up the environment variables. The LogonUser part was not working, but not necessary. – Trevor Elliott Aug 05 '15 at 21:53
  • One issue I did have was that with the sytem profile it's a different path in a 32bit and 64bit process, so I had to make sure that the WiX custom action DLL was compiled as x86 or x64 instead of "Any CPU" depending on whether my application was installed as 32 or 64bit.. The 32-bit system profile path gets virtualized to C:\WINDOWS\SysWOW64\... So it seems that any LocalDB instances you create in a 32bit process can only be used from 32bit processes, and the same with 64bit processes. – Trevor Elliott Aug 05 '15 at 21:54
  • I am having an issue with this workaround. In all my custom actions I am opening with a stub of code that sets the environment variables to the proper ones for LocalDB to work. This works on the first two custom actions where I create the instance and execute some SQL against it. However in the 3rd custom action which is pretty much identical I am getting an error that the LocalDB instance does not exist, and this is because the LocalDB native API is returning "C:\Users\[username]\AppData\..." as the instance path instead of the system path. – Trevor Elliott Aug 17 '15 at 19:19
  • However, I am doing a dump of all of the environment variables before I try to use the LocalDB API, and those variables are returning that they are set up identically and properly pointing to the "C:\WINDOWS\system32\config\systemprofile\..." folder. So it seems like this workaround of overwriting the Environment variables using Environment.SetEnvironmentVariable is not always tricking the SqlLocalDb native DLLs. – Trevor Elliott Aug 17 '15 at 19:27
  • That's why I suggested loading SYSTEM profile as well and running updates in a child process, which would inherit the environment. I think this is the only reliable way. Regarding 64-bit app and 32-bit instances, I think it's a bad idea to deploy 32-bit LocalDB to 64-bit Windows. This should be impossible in the first place, otherwise it's a bug in LocalDB MSI installer. – denis.gz Aug 18 '15 at 11:17
0

I suggest using a different user account, and not using the System account, by doing the following:-

  • create a new account on the machine, and set that to be the account under which the Windows Service runs. It's not good practice to use the system account just to run an application, anyway, as the permissions are excessive.
  • ensure that the permissions on the LocalDB files are set to allow the said user account to access the database (and thus continue to use Integrated Security)
  • make sure it works by trying to connect to the DB (once installed) under the same user account by running sqlcmd or Management Studio under the context of the said user, then connecting with Integrated Security to ensure it works.

Some other things to try/consider:

  • have you checked the Windows Event log for any events that might be useful for diagnostic purposes?
  • Make sure that if you have any other versions of SQL Server (especially prior to 2012) that for the command-line tools, the %PATH% isn't set to find an older tools version first. Older tools don't support LocalDB.
  • It is possible also (as an alternative) to set up LocalDB to be shared with other users. This involves sharing the instance, and then granting access to other users. See the "Sharing Issues" section in this article: Troubleshoot SQL Server 2012 Express LocalDB.

There's also another SO article that may contain some more useful information there in the links within (change the language in the URL from Polish to English by changing pl-pl to en-us). His work-around is using SQL Server accounts, which might not be OK in your case.

This might also be useful, as it relates to security permissions being denied, and possible resolutions: https://dba.stackexchange.com/questions/30383/cannot-start-sqllocaldb-instance-with-my-windows-account

Community
  • 1
  • 1
CJBS
  • 15,147
  • 6
  • 86
  • 135
0

Trevor, the problem you have is with the MSI custom actions. You must configure them with "Impersonate=false" otherwise the custom actions will be executed under the current user context.

BTW what tool are you using to create the installer? Depending on the tool you use, could you please provide screenshots or code snippets of your custom actions configuration?

The accepted answer from this post will give you some additional information about the different custom action execution alternatives: Run ExeCommand in customAction as Administrator mode in Wix Installer

You will find additional information about impersonation here: http://blogs.msdn.com/b/rflaming/archive/2006/09/23/768248.aspx

Community
  • 1
  • 1
Rolo
  • 3,208
  • 1
  • 24
  • 25
0

I wouldn't create the database under the system's localdb instance. I'd create it under the current user installing the product. This will make life much easier if you need to delete or manage the database. They can do this through sql management studio. Otherwise, you'll have to use psexc or something else to launch a process under the SYSTEM account to manage it.

Once the db is created, then use the share option you mentioned. The SYSTEM account can then access the database through the share name.

sqllocaldb share MSSqlLocalDb LOCAL_DB

When sharing, I've noticed you'll have to restart the the local db instance to actually access the db through the share name:

sqllocaldb stop MSSQLLocalDB

sqllocaldb start MSSQLLocalDB

Also, You may need to add the SYSTEM account as a db reader and writer to the database ...

EXEC sp_addrolemember db_datareader, 'NT AUTHORITY\SYSTEM'

  • This may suffice for some scenarios. However, I've found it's not workable in a true multi-user scenario, because the instance stops when the owner (i.e., the user who created it) logs out. And it can only be restarted by the owner. This is true even when the instance is shared. – Richard II Jul 24 '17 at 17:22
  • in relation to my previous comment: I suppose one could configure a Windows Service running under the owner/creator's credentials, so that the instance will run even when they log out, but I find that solution distasteful, as the owner has to remember to change the Service Logon password whenever they change their network password. – Richard II Jul 24 '17 at 17:39