Sunday, March 12, 2017

Some Gotchas Writing Unity Apps in F#

If you're writing your Unity code in F#, here are some useful tips that may not be immediately obvious:

Nullable Fields in Monobehaviours


A lot of Unity scripts involve classes that inherit from MonoBehaviour, some of which will have fields that become settable properties in the Unity editor. Because of to the way Unity does serialization, the values are not passed to your class' constructor. (Unity wants you to use a no-argument constructor, if any; you shouldn't be calling any Unity API methods from it.)

As a result, you need fields that initialize to null, which Unity will then in turn set. Since F# is designed to interoperate with .NET, it foresees this eventuality (while screaming at you that it's generally a bad design decision):
namespace My.Unity.FSharp

open UnityEngine

type MyBehaviour() =
    inherit MonoBehaviour()

    [<SerializeField>] [<DefaultValue>] val mutable MyGameObject: GameObject

Using val mutable with the [<DefaultValue>] attribute allows for the field to take on a "zero" value when it is initialized; for reference objects this is null.

Mutable Struct Fields


Many Unity data structures—Quaternion, for example—are implemented as .NET structs. You may find yourself bewildered at why this code doesn't work:

    let toQuat: Quaternion = Camera.main.transform.localRotation
    toQuat.x <- 0.0f

If you hover your mouse over the x in toQuat.x, the helpful hover tip will tell you val mutable x: float32. Yet you still get the error "Error: A value must be mutable in order to mutate the contents or take the address of a value type, e.g., let mutable x = ..." If you read this too quickly, it looks it's telling you x has to be mutable, but we've already seen that it is a mutable field.

The answer is that for a value type's fields to be mutable, it itself has to be mutable. So the correct code is:

    let mutable toQuat: Quaternion = Camera.main.transform.localRotation
    toQuat.x <- 0.0f 

Dependencies on Scripts in Prefabs


If you happen to be working your way through the HoloLens tutorials, as I have been, you'll reach Chapter 6 of Holograms 101, where you're given code referencing SpatialMapping.Instance.  If you write this in F#, you'll likely see Error: The namespace or module 'SpatialMapping' is not defined.

The problem is that the SpatialMapping class is part of the SpatialMapping prefab asset that you're given in the exercise; the script is built into the prefab. But writing F# code for Unity requires compiling to a plugin, so we need the SpatialMapping class to be compiled in order to be able to refer to it.

The solution is actually not difficult. If you build your Unity project, Unity will create an Assembly-CSharp.csproj file at the root of your Unity project. This project file corresponds to a .NET project that you can reference as a project reference in your F# project. Once that dependent project's DLL is built, your F# code will be able to pick up the necessary references.

Out Parameters for Complex Method Signatures


Another issue I encountered was that in some cases, Unity's APIs take an out parameter but don't make it the last parameter in the method signature. While in general F# is smart with .NET out parameters—you call the method without the out parameter, and you get back a tuple pairing a boolean with the returned value—I wasn't able to make this work with a long method signature having the out parameter in the middle, e.g.

    public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);

It's possible that the reason F# can't return a tuple is that the method signature without the out parameter collides with a different method signature of Physics.Raycast. In any event, here you have to handle the out parameter explicitly, so the F# code would look like this:

    let mutable hitInfo = Unchecked.defaultof<RaycastHit>

    if Physics.Raycast(headPosition, gazeDirection, &hitInfo, maxDistance, SpatialMapping.PhysicsRaycastMask) then
        this.transform.parent.position <- hitinfo.point 

Note how Unchecked.defaultof<'T> is used in F# to assign a null value to a binding for a reference object. The binding has to be mutable, and gets passed as an out parameter using the & prefix.

As I come up with more gotchas, I'll keep posting...

… and You Can Write HoloLens Apps in macOS / Linux Too (Part 3: Deploy to HoloLens)

In Part 1 we saw how to set up a cloud server to build Unity apps for HoloLens, and Part 2 covered how to set up a Unity HoloLens project to build in the cloud. Now we’ll look at how to get the compiled code up to the HoloLens.

One-Time Setup

To get set up to push app bundles to the HoloLens, you first need to set up the HoloLens to use the Windows Device Portal. This is a web server on the HoloLens that lets you connect and manage the HoloLens from a browser outside the device.
You set up the device portal from within the HoloLens, using the Settings app. Select the “Update” menu item and then the “For developers” menu item. Enable “Developer Mode” and then scroll down and enable “Device Portal”.

Accessing the HoloLens Via Browser

Make sure the HoloLens is connected to WiFi (make sure it’s a secure WiFi that you trust), and then look up its IP address by going to “Settings > Network & Internet > Wi-Fi > Advanced Options”.
From a web browser on a development machine in the same WiFi network, go to https://[HOLOLENS_IP_ADDRESS] .
The first time you connect, the browser should tell you that there’s a problem with the website’s security certificate. Assuming you’re on a WiFi that you trust, ignore this for the time being (we’ll fix it later).
The first time you connect, you’ll also need to create a username and password. When you connect, the first page you see should be the “Set up access” page. Click or tap “Request pin” — then if you look at the HoloLens display, you’ll see a PIN number that it generated that you should enter into the “PIN displayed on your device” textbox. After that, you can enter a username and password of your choice (the password has to have at least seven characters).  Then click “Pair” to connect to the Windows Device Portal on the HoloLens.1
Now you can fix the certificate issue. Go to the “Security” page on the Device Portal, either by clicking the “Security” link from the top right icon list or by navigating to: https://[HOLOLENS_IP_ADDRESS]/devicesecurity.htm. From that page you can download the device’s certificate.
If you’re on macOS, you’ll need to add a .p12 extension to the certificate name. Then you can double-click it and it will install into your keychain. Next, double-click it in the keychain and next to “Secure Sockets Layer (SSL)” select “Always Trust”.
If you’re on Windows, from the “Run” app, type “Manage Computer Certificates” and start the applet. Expand the “Trusted Root Certification Authority” folder, and click on the “Certificates” folder. Then, from the Action menu, select: “All Tasks > Import…”  Once you complete the Certificate Import Wizard using the downloaded certificate file, you will have installed the certificate in the Trusted Root Certification Authorities store on your local machine.
(I’m not sure what needs to be done on Linux; if someone fills me in I’ll add it here.)
You’ll need to restart your browser for it to recognize the imported certificate.

Deploying Your App To the HoloLens

Now in the Windows Device Portal, click “Apps”. Under “Install App” you can select your app bundle: Click the “Choose File” button under “App package” and navigate to the network share from the remote build server, and drill down under the AppPackages directory under the solution directory of your IL2CPP build. You want the file named after your project with the extension .appxbundle.
Then under “Deploy”, click “Go” to deploy the app package to the HoloLens.
It’s as easy as that!

[This piece was originally posted here.]

1 If you want to change the username or password later, you can visit the device security page by either clicking the Security link along the top right, or by navigating to: https://[HOLOLENS_IP_ADDRESS]/devicesecurity.htm .

… and You Can Write HoloLens Apps in macOS / Linux Too (Part 2: Project Setup)

In Part 1 we saw how to set up a cloud server to build Unity apps for HoloLens. That was setup you only need to do once. For remote builds, however, you’ll also need to do a bit of setup for each project you create.

Set Up Your Remote Git Repository

You’ll need a place to push and build your project on the remote server, so you’ll need to initialize a repository. However, we can’t just initialize a bare git repository, because we’ll need the files to be checked out.
Here are the commands to run in git bash which should already be installed in your VM to get things set up (the items in brackets are things you’ll need to fill in):
# One-time git setup; git will be annoyed at you if you don't do this.
git config --global user.email [YOUR_EMAIL]
git config --global user.name [YOUR_NAME]

cd /c/Users/[USER]/[PATH_TO_DIRECTORY_CONTAINING_REPOSITORIES]
git init [PROJECT_NAME]
cd [PROJECT_NAME]

# Allow pushing to checked-out (non-bare) repository
git config receive.denyCurrentBranch updateInstead
Now you’ll need  to set your local git repository up to push to the remote repository. If you use the script from Part 1 to modify your /etc/hosts file when you connect to the remote server, you won’t need to modify the git remote URL each time the remote VM starts up and gets a new IP address.
To get set up, you’ll need the Unity project on your local development machine to be checked in to git. Now you can run
git remote add holodevelop ssh://[WINUSER]@holodevelop/Users/[PATH_TO_REPO_WITH_FORWARD_SLASHES]
Note the forward slashes in the URL, and also the fact that for some reason there’s no “C:” in the path. I’m not sure why this is the case.
Note also that instead of “origin,” I call the remote repository “holodevelop,” but you can call it what you want. Note in addition that the remote server alias is also “holodevelop,” which should correspond to the alias the script creates in your /etc/hosts file.
Now you’ll be able to push your git repository to the remote server. For example, to push the master branch (if the remote repository is called “holodevelop”) the command would be:
git push holodevelop master
Since you’re pushing to a repository that has files checked out in the workspace, there are a few things you need to take note of. If you have unstaged changes in the remote repository which Unity sometimes initiates or if the remote gets ahead of the local repository, git won’t let you do a push; it will tell you you have unstaged changes in the remote directory, or that the remote contains work you don’t have locally. You’ll need to commit any unstaged changes into the remote repository first, then pull them back to your local repository (git pull holodevelop master) and merge them in if necessary.
With the setup so far, every time you push with git you’ll be asked for your password. To use public key authentication, put the following in your ~/.ssh/config file (assuming the remote host alias in /etc/hostsis holodevelop and your private key is named holodevelop):
Host            holodevelop
    Hostname        holodevelop
    IdentityFile    ~/.ssh/holodevelop
    IdentitiesOnly  yes

Pushing Up Your DLLs

For those building in F# or some other language not directly supported by Unity scripting, you’ll be compiling your script code into DLLs which will need to be copied into the Assets/Plugins directory of your Unity project. Since it’s not a good idea to put compiled artifacts into git, we’ll use a different approach.
First, make an Assets/Plugins directory in your Unity project and put an empty file named .gitkeep in there, to make sure the folder exists when you push your git repository to the remote server.  You’ll also want to add Assets/Plugins/*.dll* (and if you’re on macOS, .DS_Store) to your .gitignore file.
To get the DLLs onto the remote server, you’ll need a post-build step that copies them to the right place. Since we’ve already mounted a file share on the remote server, this should be as easy as doing a copy command.
One way to accomplish this is to add a post-build step to your MSBuild project file, but this will make your project file not work across platforms. (I also tried it, and it isn’t evident how to do it if you want to go this route I suggest using Xamarin Studio to edit the build configuration and add the post-build step.
I use the FAKE build tool for building F# projects; the Ionide IDE is also integrated with Forge, which creates the whole F# project for you. For the post-build step, I change the Deploy target in the build.fsx file to the following
let remoteProjectPath = "[PATH_TO_PROJECT_ON_FILESHARE]"

Target "Deploy" (fun _ ->
    let myCodeDll = buildDir + "[PROJECT_NAME].dll"
    let fsharpDll = buildDir + "FSharp.Core.dll"
    Copy "../Assets/Plugins" [ myCodeDll; fsharpDll ]
    Copy (remoteProjectPath + "/Assets/Plugins") [ myCodeDll; fsharpDll ]
)
I also change the RunTargetOrDefault entry at the bottom of the file to “Deploy”. Now in the Ionide command menu I can enter “FAKE: Build Default” and the DLLs will be copied to the Assets/Plugins directory in both the local and remote projects. NOTE: To prevent getting a “Lock violation on path” error when trying to copy up the dlls, don’t have the remote folder open or even visible in your Finder window (smb will lock it).

Doing a Unity Build

Before you can run a Unity headless build for HoloLens, you’ll need to do a few things.
First, annoyingly, Unity apparently needs to reactivate itself every time you start the VM, so each time you start the VM, or even restart Windows on the VM, you’ll need to open Unity. (I’m hoping Unity support can give me some workaround for this.)
The first time you create a HoloLens project on the remote box, you have to open it in Unity to add a few settings that can only be added when you’re running Unity in Windows:
  • In the build settings, make sure you Switch Platforms to Windows Store. Set the SDK to Universal 10 and the target device to HoloLens.
  • In the player settings, click the Windows Store tab and go to Other Settings > Rendering. Check the “Virtual Reality Supported” box, and make sure Windows Holographic is under the Virtual Reality SDKs.
  • In the player settings, under  Configuration > Scripting backend  pull down IL2CPP.
  • Save the project, commit, and pull any changes back to local machine.
If you’re compiling F# or other code to plugin .DLLs, before you close the project you’ll also need to make sure the MonoBehaviours in your code are attached to the relevant objects. Don’t delete the DLLs while Unity is open, or they’ll get detached and you’ll have to reattach them.
Now you’ll need a headless build script for Unity. This comes in two parts. The first is a Unity-flavored Javascript file that has to go in Assets/Editor, and the second a batch file that can go at the top-level of your Unity project.
Here’s the Javascript file; I call it build.js:
import System.Diagnostics;

class Autobuild {

    static function Build() {
        // The path to the directory where the IL2CPP files will be built.
        var path = "C:/[PATH_TO_OUTPUT_DIR]";
        // An array of paths to the files for the scenes you want to build.
        var scenes : String[] = [ "Assets/[SCENE_NAME].unity" ];
        // Build the player.
        BuildPipeline.BuildPlayer(scenes, path, BuildTarget.WSAPlayer, BuildOptions.None);
    }
}
You’ll need to fill in the path where you want the C++ files build, as well as the scenes you want to include. Note that the path starts with the drive letter, and the path dividers are forward slashes.
Now you’ll need a batch file to trigger the build; we’ll put this in unity_headless_build.bat at the root of the Unity project:
SET UNITY_FOLDER=[UNITY_VERSION_NAME]
SET OUTPUT_DIR=[PATH_TO_OUTPUT_DIR]

cd %~dp0
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"

"C:\Program Files\%UNITY_FOLDER%\Editor\Unity.exe" -batchmode -quit -projectPath %~dp0 -executeMethod Autobuild.Build
Here you need to fill in the name of the folder where Unity is installed (the full path is created further down) and the path to the directory where the IL2CPP files will be build. This needs to be Windows-style, with backslashes.
You should be able to run this batch file from the Windows command prompt and see a new Visual Studio project built in your target directory.

Running MSBuild

To get the final executable you can run on the HoloLens, you now have to build the project that Unity generated. You can do this by running msbuild using the following script, which I call msbuild_headless.batand put in the root of my Unity project:
SET OUTPUT_DIR=[PATH_TO_OUTPUT_DIR]
set SOLUTION_FILE_NAME=[PROJECT_NAME].sln
cd "%OUTPUT_DIR%"

msbuild.exe %SOLUTION_FILE_NAME% /t:Build /p:Configuration=Release;Platform=x86;AppxBundle=Always
Here again the path to the project should use the Windows-style path, and you’ll also need to fill in the name of the solution file generated by Unity.
Running this script at the command prompt should give you a new AppPackages directory inside the solution directory, and several levels under that you’ll see a file named after your project with the extension .appxbundle. This is the executable you’ll upload to your HoloLens (or deploy to the emulator).
If you want to run everything from your local command line in one step, you can make a remote_build.sh script as follows:
ssh [USER]@holodevelop "cd [UNITY_PROJECT_DIR] && unity_headless_build.bat && msbuild_headless.bat"
Here, you should fill in your user ID on the remote machine and a Windows-style path to the Unity project directory.
This should give you all you need to run a remote build of your application. In the last part of this series, we’ll look at how to deploy the application to the HoloLens.

[This piece was originally posted here.]

… and You Can Write HoloLens Apps in macOS / Linux Too (Part 1: The Cloud Server)

Interested in doing all your HoloLens development while running macOS or Linux? Well, you can’t — not all of it, anyway. HoloLens applications have to be compiled to UWP applications by the .NET Native compiler, which only runs on Windows. And Unity only builds Windows Store apps on Windows. Both Microsoft’s and Unity’s emulators only run on Windows.
But..
What you can do is write your Unity code locally on macOS or Linux, push it up to a remote build server, and build it there. This might also be a desirable option if you have an underpowered Windows machine and want to speed up your builds.
And, depending on your usage, you can have the cloud server free of cost for a year.

Caveats

If you don’t have a HoloLens, this setup might not be for you. Currently there is no way to run either Microsoft’s or Unity’s emulator in the cloud.1 So if you want to develop on Mac or Linux your best bet is to get yourself a HoloLens to test on. (Or, you could get a Windows machine just for build and emulation, in which case some of the information below will still be of use to you.)
There’s also the issue of debugging. You can use the Visual Studio debugger on apps running in the emulator or on even on the device; there may be ways to do this from Xamarin Studio or the command line, but I haven’t tried to make that work.

Setting Up the Cloud Server

For this article, we’re going to use Microsoft’s Azure cloud service, though you could also use Amazon’s AWS or other cloud services. One advantage of Azure is that as a developer, you can sign up for Visual Studio Dev Essentials and get $25 per month of complimentary Azure credit for your first year.2 Depending on the size of the VM you choose, you should be able to stay under that budget.
Once you’ve signed up, go to the Subscriber Portal, find the Azure feature, and click the “Activate” link. This will set you up with your Azure account and give you a dashboard, as well as a screen where you can view your developer subscription and see how much credit you’ve used for the month.
The dashboard is where you create the virtual machine on which your build server will run. Here’s how:
  1. In the portal menu on the right, select + New > Compute
  2. Hit the “See All” link
  3. Search for “Visual Studio Community 2017 RC on Windows Server 2016 (x64)”
  4. Click on it, then select the “Resource Manager” Deployment Model
  5. For the Basics step:
    • Give your VM a name.
    • Choose an SSD disk.
    • Create a username and password.
    • Use your “Developer Program Benefit” subscription
    • Create a new “resource group”3 (unless you have one you already want to use) and give it a name.
  6. For the Size step, choose your VM size. To fit within the developer benefit budget, you’ll probably need to use a DS2_V2, depending on how much you use the VM. Ideally you’ll want more than 8GB of RAM, and 4 cores is as many as you can get with the developer license, so you might prefer an F4S or DS3_V2 instead. Note that you won’t be using the VM 24×7, so the monthly prices are higher than what you’re going to pay, but you may have to limit your usage to fit inside the $25/month credit.
  7. For the Settings step, choose Managed disks (there’s no price difference, and it’s easier), and otherwise leave the defaults.
  8. Once you create the VM, you should see it in your dashboard, at least under “All Resources.” If not, click on “All Resources.” You can click on your VM to see it, and it will then appear under “All Resources.”

Connecting to the Server

Before you connect to the VM, you’ll need a remote desktop client. For macOS I suggest the Microsoft Remote Desktop Beta, but you can also get the non-beta version on the app store.4 For Linux, there are several RDP clients out there you can choose from. (I’m not using Linux, so I can’t make recommendations.)
To connect to the server, double-click on your VM in the Azure dashboard. You’ll see a row of buttons at the top the leftmost of which is “Connect.” Clicking it will cause you to download a file with the .rdp extension. On macOS, double-clicking the RDP file should open a connection to the remote server in the Microsoft remote desktop client.
Note that if you don’t want to spend the extra money for a static IP address, the IP address will change every time you restart the virtual machine. Since you’ll be using various automated scripts, you’d need to change the IP address in each one every time. Instead, you can write a script to read the IP address from the RDP file and modify /etc/hosts with the new IP address. Then you can use the alias in /etc/hosts in your scripts without having to modify them.
Here’s an example of such a script, which you can modify and run whenever you download a new RDP file; it uses the alias holodevelopfor the remote server. (This script is for macOS but a Linux version would be similar.) The script will automatically open the desktop client for you once it finishes modifying the hosts file, and then mount the \Users folder on the remote system, which the Azure VM should automatically share with you:
#/bin/sh

# May need to modify this for whatever directory the rdp files get downloaded to
RDP_FILE=`ls -tr ~/Downloads/*.rdp | tail -1`

# sed -E is for OS X, use sed -r on linux
IP_ADDRESS=`cat "$RDP_FILE" | grep 'address' | sed -E 's/.*s:(.*):[[:digit:]]+/\1/' | tr -d '\015' | tr -d '\012'`

# Before running this script for the first time, initialize /etc/hosts with:
# 0.0.0.0 holodevelop

# Dig the remote IP address out of the RDP file
sudo sed -e 's/.* holodevelop'"/$IP_ADDRESS holodevelop/" -i old /etc/hosts

# This will open a session to the server in Microsoft Remote Desktop
open "$RDP_FILE"

# You'll need to add your remote user ID for USER and a path to a local directory LOCAL_PATH where you'll mount the drive
mount -t smbfs "//[USER]@$IP_ADDRESS/Users" "[LOCAL_PATH]"
Before this script can mount the remote file share, you have to tell Azure to let requests on port 445 access the server:
  • In the Azure web portal, find the Network Security Group you created. (You may have to go to Resource Groups and click link on your resource group to find it.)
  • Click on Inbound Security Rules > Add
  • Use the following settings — Source: Any, Service: Custom, Port Range: 445, Action: Allow
Note that when the script mounts the remote file share, it will ask you for your password on the remote machine.

Basic Server Setup

Once you remote in to your VM, here are some things to check and changes I recommend:
  • You should see Visual Studio 2017 Community and Unity already installed.
  • You should also see the directory C:\Program Files (x86)\Microsoft Visual Studio Tools for Unity.
  • You should see Git installed in C:\Program Files\Git.
  • You may want to set your time zone on the server in Settings > Time and Language > Date and Time
  • Turn off IE Enhanced Security Configuration, or your experience downloading additional software will be miserable. (You can turn it back on later, but this may not be the kind of server where you worry too much about locking down the browser.) To do this, go to Server Manager > Local Server, locate “IE Enhanced Security Configuration” in the right-hand column and click the link next to it. You should see a screen that lets you turn it off.
  • Add C:\Program Files\Git\mingw64\bin and C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin to the System PATH environment variable. (Use Control Panel > System and Security > System, click the Advanced System Settings on the right, and hit the Environment Variables button at the bottom.) The first is needed for remote git to work, and the second for msbuild.exe to be accessible by your build scripts.
  • Create a directory where you will put your git repositories for your projects. Mine is Documents/holodevelop.
Now it’s time to install a few things you need.

Install the Unity Beta

If you’re going to be doing IL2CPP builds from Unity — and this is the future of Unity scripting, not to mention a necessity for writing Unity scripts in F# — you’ll want to download and install the latest Unity beta, since they’re working hard on improving the performance of this feature. At the installer options, uncheck the standard assets, documentation, and download of Visual Studio 2015, and check the Windows Store il2cpp scripting backend.
After installing Unity, make sure it’s pointing to the path to Visual Studio 2017 as the script editor. (This isn’t strictly speaking necessary, since you’re going to do headless builds, but it will help if you decide to do any work directly on the remote server.)
  • Open Unity; Go to Edit -> Preferences, and make sure that Visual Studio 2017 Community is selected as the External Script Editor
  • If not, choose Browse in the External Script Editor pulldown, and point to the Visual Studio executable (which should be C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe)

Install and Set Up SSH

We will use git over SSH to transfer code from your local machine to the server.5 
Download and install the Bitvise Secure Windows Remote Access server, which is free for non-commercial use. Run the installer and accept the default install, check “Run Bitvise SSH Server Control Panel when done,” and select the Personal Edition.
After you’ve finished, you’ll see a Settings page. Click the “Open easy settings” link.
  • For Open Windows Firewall: Pull down “Open port(s) to any computer”
  • Click the “Next” button
  • On “Windows Accounts” unclick “Allow Login to Any Windows Account”
  • On “Windows Accounts” click “Add” and add your own Windows account.
Now back on the main control panel screen, click the “Start Server” link (just above the Host Keys).
Before you can use SSH, you have to tell Azure to let requests on port 22 access the server:
  • In the Azure web portal, find the Network Security Group you created. (You may have to go to Resource Groups and click link on your resource group to find it.)
  • Click on Inbound Security Rules > Add
  • Use the following settings — Source: Any, Service: SSH, Action: Allow
Now set up public-key authentication, which will allow scripts to transfer files without you having to enter a password each time.
    • On your local machine, run the command ssh-keygen -t rsa
    • Note that you don’t want a passphrase for this key, because it will be used by automated programs. Keep it safe!
    • Name the private and public key files something that will distinguish them from other keys you might have, e.g., holodevelop.
    • Generally it’s customary to keep the key pair in your ~/.sshfolder.
    • Copy the public key up to the Windows remote server using the shared folder you created.
    • Import the public key on the remote server. From the Start menu, open the Bitvise SSH Server Control Panel, click the “Open easy settings” link, click the “Next” button, and select your own account in the “Windows Accounts” screen. Click on the “Public Keys” link and add the public key that you copied up in the last step.
    • Now you can delete the public key file you copied up from the remote Windows machine.
    • Restart Windows.
Now test to see if you can connect using your public key, for example:
ssh -i ~/.ssh/holodevelop [USER]@holodevelop

Modify Windows Defender

Windows Defender is an antivirus program that does security scans of  the local system. Subjecting your git repository and your build directories to Windows Defender scans may slow your build. We’ll exclude these directories from being scanned by Windows Defender:
  • Settings > Update & Security > Windows Defender
  • Scroll down to Exclusions and click Add an Exclusion
  • Exclude the folder that will hold your git repository as well as the folder you created on the E: drive for builds).

Some Last Thoughts for Part 1

When you’re done with the VM, stop it so that you’re not incurring charges for the VM time. You’ll need to remember to restart it when you want to do development. To stop a VM go into the Azure portal, click on the VM, and hit Stop from the buttons at the top row.
Now we’re ready for Part 2, where we’ll do one-time project-specific setup.

[This piece was originally posted here.]

1 Microsoft’s HoloLens emulator requires Hyper-V, and since Azure VMs are already virtualized, and you can’t have nested Hyper-V, it won’t work. (I’ve tested this.) If you want to run Microsoft’s emulator, you’ll need a machine running Windows Pro or some other edition of Windows that supports Hyper-V.
Unity’s Holographic Emulation also has problems in a VM environment.
2 Microsoft also has a build service included in Visual Studio Team Services, which is free of charge for teams of fewer than five members. Unfortunately, it currently doesn’t support Unity builds.
3 A resource group holds everything you’re going to create — not just the virtual machine, but the network interface, IP address, network security group, storage, etc. This is so that if you want to get rid of a VM you can get rid of the entire group and not continue to be charged for resources outside the VM, such as storage.
4 One productivity tip: Once logged in to a remote server using the macOS remote desktop client, if I Cmd-Tab switched from that app, I found myself unable to Cmd-Tab back to the remote screen; I’d always come back to the “View Connection Center” window of the desktop client, and then have to double-click on my active connection to switch to the main screen. To be able to Cmd-Tab back directly to the remote screen, minimize the View Connection Center window after connecting.
5 I tried doing this by having git use the drive shared from the server, but git doesn’t play nice with smb protocol, at least not when going from macOS to Windows.
6 The -a switch creates a new disk, -t puts it in virtual memory, -s is the size, -m creates it as drive E:, and the options to -p say to create an ntfs file system, do a quick format on it, and automatically answer “Yes” to the user prompts. The second line creates a holodevelop folder in the E: drive. (You can name it whatever you want instead.)
7 Each time you log in to the VM the script will run, but it’s idempotent so nothing is harmed by running it more than once. You could have the E: drive created whenever Windows starts by creating a Windows service, but that’s is more complicated. Also, imdisk has a -P parameter which will create the drive on Windows startup, but it won’t format it, so there would be a manual step involved.