Windows IIS Deployment

I understand a web app can be deployed with IIS or as a standalone service in Windows. I read that IIS is the preferred way.
Is there any up-to-date instructions on deploying on Windows?

I went to the documentation:
Deploying web apps on IIS — Xojo documentation

The documentation provides the following link to IIS deployment instructions (not supported by Xojo):
Real Studio Web Edition Stand Alone Windows Server Deployment (osswald.com)

It states that Microsoft Web Platform Installer (WebPI) is required. However on Microsoft’s website, it says that it was retired on Jul 1, 2022:
Web Platform Installer : The Official Microsoft IIS Site

So if anyone has any up-to-date instructions that are easy to follow on deploying on Microsoft IIS, I’d appreciate it.

In addition, if you have instructions on deploying as a standalone app, that would also be helpful.

I’m not too sure on the pros and cons of deploying using either method so any explanation would of appreciated.

Standalone or as a service is the only deployment options as far as I know

Just drop your app into ProgramFiles, or you can even create an InnoSetup Installer if user installation is required. Run your app as a Windows Service, so that it will relaunch if there are any issues. You will need to punch a hole in your firewall for your app as well.

I use the following Method within my app to create a Service in the Opening Event by passing the EXE to itself:

Protected Function getCreateWindowsService(appFile As FolderItem, Port As Integer = -1, SecurePort As Integer = -1, isStartService As Boolean = True, isAutoStart As Boolean = True, isFailureRestart As Boolean = True, FailureRestartResetSeconds As Integer = 120, FirewallIn As Boolean = True, FirewallOut As Boolean = True) As String
  #If TargetWindows And Not DebugBuild Then
    '#Pragma Unused isStartService
    '#Pragma Unused isAutoStart
    '#Pragma Unused isFailureRestart
    '#Pragma Unused FirewallIn
    '#Pragma Unused FirewallOut
    '#Pragma Unused FailureRestartResetSeconds
    
    Var ShellCommand As String
    Var tempShell As New Shell
    Var ReturnResults As String
    Var ServiceName As String
    
    If appfile = Nil Or Not appFile.Exists Then Return "Application not found"
    
    'If ServiceName = "" Then ServiceName = appfile.NameWithoutExtensionMBS
    ServiceName = appfile.NameWithoutExtensionMBS
    ServiceName = ServiceName.ReplaceAll(" ", "") 'in case there are spaces in the file name!
    
    'sc queryex type= service state= all
    ShellCommand = "sc queryex type= service state= all"
    tempShell.Execute(ShellCommand)
    If tempShell.ExitCode <> 0 Then ReturnResults = ReturnResults + tempShell.Result + EndOfLine
    
    If tempShell.Result.IndexOf(ServiceName) < 0 Then 'only add the Service if it doesn't already exist ie once!
      'stop any old Windows Service
      'ShellCommand = "sc stop " + ServiceName
      'tempShell.Execute(ShellCommand)
      'if tempShell.ExitCode <> 0 then ReturnResults = ReturnResults + tempShell.Result + EndOfLine
      
      ''delete any old Windows Service
      'ShellCommand = "sc delete " + ServiceName
      'tempShell.Execute(ShellCommand)
      'if tempShell.ExitCode <> 0 then ReturnResults = ReturnResults + tempShell.Result + EndOfLine
      
      'create the new Service
      'sc create BamBamCloudHTML
      'ShellCommand = "sc create " + ServiceName + " binpath= " + Encodings.UTF8.Chr(34) + appfile.NativePath + Encodings.UTF8.Chr(34) + If(isAutoStart, " start= auto", "")
      'ShellCommand = "sc create " + ServiceName + " binpath= " + Encodings.UTF8.Chr(34) + appfile.NativePath + " --port=9000 --secureport=443" + Encodings.UTF8.Chr(34) + If(isAutoStart, " start= auto", "")
      ShellCommand = "sc create " + ServiceName + " binpath= " + Encodings.UTF8.Chr(34) + appfile.NativePath + If(Port = -1, "", " --port=" + Port.ToString) + If(SecurePort = -1, "", " --secureport=" + SecurePort.ToString) + Encodings.UTF8.Chr(34) + If(isAutoStart, " start= auto", "") 'no port/secureport needed for Console Service
      tempShell.Execute(ShellCommand)
      If tempShell.ExitCode <> 0 Then ReturnResults = ReturnResults + ShellCommand + EndOfLine + tempShell.Result + EndOfLine
      
      'set the Service to restart twice after two seconds then if it still fails then wait for FailureRestartResetSeconds seconds
      If isFailureRestart Then
        ShellCommand = "sc failure " + ServiceName + " actions= restart/2000/restart/2000// reset= " + FailureRestartResetSeconds.ToString
        tempShell.Execute(ShellCommand)
        If tempShell.ExitCode <> 0 Then ReturnResults = ReturnResults + ShellCommand + EndOfLine + tempShell.Result + EndOfLine
      End If
      
      If isStartService Then 'allow the user to start the service manually, otherwise we can never run the app in a command window
        ShellCommand = "sc start " + ServiceName
        tempShell.Execute(ShellCommand)
        If tempShell.ExitCode <> 0 Then ReturnResults = ReturnResults + ShellCommand + EndOfLine + tempShell.Result + EndOfLine
      End If
      
      'add the application to the Windows Firewall for incoming traffic. Problem is that it add the rule many times!
      If FirewallIn Then
        ShellCommand = "netsh advfirewall firewall add rule name=" + Encodings.UTF8.Chr(34) + ServiceName + Encodings.UTF8.Chr(34) + " dir=in action=allow program=" + Encodings.UTF8.Chr(34) + appfile.NativePath + Encodings.UTF8.Chr(34) + " enable=yes "
        tempShell.Execute(ShellCommand)
        If tempShell.ExitCode <> 0 Then ReturnResults = ReturnResults + ShellCommand + EndOfLine + tempShell.Result + EndOfLine
      End If
      
      'add the application to the Windows Firewall for outgoing traffic.  Problem is that it add the rule many times!
      If FirewallOut Then
        ShellCommand = "netsh advfirewall firewall add rule name=" + Encodings.UTF8.Chr(34) + ServiceName + Encodings.UTF8.Chr(34) + " dir=out action=allow program=" + Encodings.UTF8.Chr(34) + appfile.NativePath + Encodings.UTF8.Chr(34) + " enable=yes "
        tempShell.Execute(ShellCommand)
        If tempShell.ExitCode <> 0 Then ReturnResults = ReturnResults + ShellCommand + EndOfLine + tempShell.Result + EndOfLine
      End If
      
      'If ReturnResults = "" Then
      'Return "No error"
      'Else
      'Return ReturnResults
      'End If
    End If
    
    Return ReturnResults
  #EndIf
      
End Function
2 Likes

Thanks. If running as a Windows Service, can it not be set to Automatic startup type such that the service restarts automatically if the PC is restarted or other issues occur? In which case, your code won’t be necessary?

That code installs itself as a service on the first run it seems. So you dont need to do it manually

@Per_Ronny_Westereng is right, you must run the ‘sc create’ at least once. You can do it manually, but it is much easier for the client if it happens automatically when they first run the app.

I have it run on every Opening Event, but if the Service name already exists, the Shell command does nothing on the subsequent runs.

Hi Samiul, I’ve read everyones replies, and I’m by NO MEANS an expert, but I was able to deploy our stand-alone intranet apps on a Windows 10 Server with only a few easy commands, and no code additions to my IDE/Project file. If you are still struggling I’ll be glad to share my steps. Good luck!

To install the Application-Request-Rewrite component on Windows Server 2k16, 2k19 & 2k22 download the URL-Rewrite component first then the Application-Request-Routing component. There is no need to download & run the Web Platform Installer.

Once done you have the ability to write the reverse proxy rules for the website.

Also if you wish to use the service recovery option (SC Failure) then you’ll also need to run
sc.exe failureflag _ServiceName_ 1 which tells the Service Control Manager to treat application crashes as if they are service crashes.

1 Like

Hi William.
Yes please could you share your instructions if you don’t mind. I’ve had IT support look into it, and they report that the Xojo test app (Deployment overview — Xojo documentation is not successfully becoming a background service when trying to deploy it. I hope it is indeed just a few simple steps to follow so I can point them in the right direction.
If possible, I’d like the web app to direct to a domain alias on the server (rather than having to type in IP address to access it).

They also state that binding of port 80 is already in use by another application. Can we use a different port? If so, how?

I am not so focussed on getting the app to relaunch if the service is interrupted just yet. But would like to get the Windows Service working in the first instance. @David_Cox you mentioned to just drop the .exe into the Program Files. What else do I need to do successfully get it started as a Service?

Drop the app into Program Files. Right-click on the EXE and choose Run as Administrator in a Command window. My code will create the Service. Quit the Command window. Check it is running in the Services app.

If you change your mind on the port number, delete the Service using ‘sc delete ServiceName’ and run again with the new port number.

Thanks @David_Cox.
I will give your code a go and create the service programmtically from the app Opening event. Although I’m keen to know what the manual method involves.
I take it I can ignore all your commented lines of code in the getCreateWindowsService function?

@David_Cox can I access the app on-premise using a domain alias from any client browser instead of IP address? If so, how can I achieve this?

Absolutely!

I know very little about this but want to be able to install on-premise on Windows machines. @David_Cox your code above in the Opening event is great to get the standalone running reliably as a service (pretty sure, haven’t had a chance to try it yet) but how to get something up and running using IIS ? Assume that IIS does load balancing ?

What I really want is something like @Tim_Parnell 's Lifeboat for Windows tbh.

Have a look at Deploying a Web App to a Windows Intranet Server in less than 10 minutes

1 Like