Random IT Utensils https://blog.adamfurmanek.pl IT, operating systems, maths, and more. Wed, 24 Apr 2024 20:00:37 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 Availability Anywhere Part 26 — Working remotely like a pro https://blog.adamfurmanek.pl/2024/04/24/availability-anywhere-part-26/ https://blog.adamfurmanek.pl/2024/04/24/availability-anywhere-part-26/#respond Wed, 24 Apr 2024 19:59:55 +0000 https://blog.adamfurmanek.pl/?p=5004 Continue reading Availability Anywhere Part 26 — Working remotely like a pro]]>

This is the twentieth sixth part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

Over the years I improved my remote work setup significantly. This blog post summarizes what various approaches I took and how I configured my setup over the years. One note before moving on – I assume you don’t have any “hardware” but the computer (so no programmable boards, weird mobile devices, confidential hardware etc.).

Level 1

First approach is what most of us consider “remote work”. You just take your corporate laptop with you and you work remotely. You probably have a corporate VPN that lets you connect to the company’s resources from wherever you are.

Pros:

  • You can do everything the same way you did while in the office

Cons:

  • You can’t work from devices that are not onboarded with your corporate infrastructure – mobile phones, tablets, private laptops, VR goggles, etc.
  • You need to take the corporate laptop with you anywhere you go
  • You work from a device that is probably hot and loud working crazy on your office stuff (we all know the bloatware they install on the machines…)
  • You can rarely pick your hardware, so your laptop is probably big and bulky (and yes, Macbook Air is big and bulky)
  • You may not be able to connect to the VPN in some places due to weird networking conditions

While this is enough to actually do the work, I don’t like this approach. First, I always want to be able to be online (that doesn’t mean that I’m online all the time) no matter where I am and what I have on me. Specifically, I want to be able to access the infrastructure and do all my stuff using my mobile phone, private laptop, etc. I mean, I want to be able to do coding or video processing from my mobile phone while on a train (yes, I did that).

Second, I don’t like taking my corporate machine with me. This is especially troublesome when I need to travel for conferences for which I need to take more hardware with me (like 2 laptops and many mobile phones). Taking another laptop on the plane is just cumbersome. This is sometimes even dangerous depending on where you go and when they want to control your corporate machines. Not to mention, that your corporate policy may prohibit you from taking the corporate laptop to some places.

Third, I don’t like when I can’t pick my hardware. My laptop must be light and quiet, especially when I sit in a deckchair or lie down.

Level 1.5

Sometimes you cannot connect to the VPN while working remotely. This is due to weird networking, policy, or simply geolocation block.

You can fix that by getting a networking device that will connect to some other VPN and expose a tunnel for you. Typical solution is to get a router with built-in VPN. You can see this tutorial to learn how to do that.

However, I don’t recommend taking a router for that. Just get yourself a decent Windows x86 tablet, for instance Dell Venue 8 Pro and install SoftEther VPN. First, such a tablet is a “full Windows machine” so you can do whatever is needed to get connected (think about captive portals or weird passwords). Second, since this is a tablet, you can take it with you wherever you need.

Level 2

Let’s not bring the corporate machine with us at all. Get yourself a corporate virtual machine (typically called virtual desktop or VDI) and do all the work from it. Next, to work remotely, you just need to connect to the VDI from your device (which we’ll call road runner) using RDP + SSH. To do that, you may need to onboard your road runner with your corporate VPN. However, there are many solutions to work that around, so from now on we assume your road runner is not onboarded with the corporate bloatware.

Pros:

  • You can do almost everything the same way you did while in the office
  • You can work from your laptop
  • You can take your hardware anywhere you wish

Cons:

  • You may still be unable to work from mobile phones, tablets, VR goggles, etc.
  • You may need to run video conferencing software on your road runner (which may not be possible)
  • Your road runner may still get hot and loud when taking crazy video call with greenscreen effects
  • You may not be able to do some corporate tasks – think about GPU-heavy things like video processing

This is much better now since we can connect from our road runner, so we can get a light laptop like Acer Swift 5 which is 1kg/2.2lb (your Macbook Air is at least 25% heavier).

When it comes to video conferences, they may be tricky. First, your webcam may not work over RDP. Second, RDP introduces noticeable audio delay. To fix that, you may need to run the conference software locally (I recommend doing that in browser). While it solves the problem, it also makes your road runner loud and hot.

Level 3

Let’s finally address the problem that we can’t work from any device. Let’s add a jump host. Get yourself a decent Windows machine and put it somewhere on the Internet. I generally recommend Hetzner (but whatever you choose, just get yourself a dedicated host). And BTW, choose Intel CPU instead of AMD (your virtual machines will thank you for that). Next, connect to that machine with plain RDP. This way, you can connect to it using any road runner – be it laptop, mobile phone, tablet, or whatever else. And since it can be a mobile phone, just get yourself a decent foldable. Yes, that really makes a difference when you’re working remotely.

Pros:

  • You can do almost everything the same way you did while in the office
  • You can work from nearly any device
  • You can take your hardware anywhere you wish

Cons:

  • You may need to run video conferencing software on your road runner (which may not be possible)
  • Your road runner may still get hot and loud when taking crazy video call with greenscreen effects
  • You may not be able to do some corporate tasks – think about GPU-heavy things like video processing
  • You may have hard time connecting to the corporate VPN

When dealing with corporate VPN, use things like SSLH or FileProxy for avoiding VPN without split tunneling (also known as TCP over File System). There are many more tricks and improvements you can apply. I cover them in my talk.

Level 4

Let’s now deal with more devices and the GPU. I use two solutions for that: vSpatial and NoMachine.

First, vSpatial lets you connect to your jump host with browser and application for VR goggles. This way, you can work from basically any device you can think of. Another bonus is that vSpatial lets you forward audio and camera, so you can take your meetings from the jump host. However, as of today, vSpatial supports only 20 FPS for your webcam, so people will notice that something is off. We’ll deal with that in a second.

Second, NoMachine lets you connect to the jump host over VNC-like protocol which lets you use GPU easily. You can now do basically anything you can think of.

Pros:

  • You can do everything the same way you did while in the office (assuming your VDI can do the things your corporate laptop could)
  • You can work from any device
  • You can take your hardware anywhere you wish

Cons:

  • You may need to run video conferencing software on your road runner (which may not be possible)
  • Your road runner may still get hot and loud when taking crazy video call with greenscreen effects
  • You may have hard time connecting to the corporate VPN

Nothing stops you from having more jump hosts. For instance, you can host a dedicated Mac mini in Cyberlynk and connect to it.

Also, if you need more screens with your road runner, you can try portable monitor. You can also emulate screens with IddSampleDriver or its Rust version, or Better Display. This way you can literally move between machines with not a single window changing its position.

Level 5

Let’s finally deal with our road runners becoming loud and hot. We basically want to offload whole video processing to the jump host so that our device doesn’t need to encode/decode any video conferencing stuff.

To do that, use VDO.Ninja. This lets you “forward” your audio and camera to any jump host. Just use OBS or Splitcam on the jumphost to receive the video, apply any greenscreen effects you need, and forward it to the conference call.

Pros:

  • You can do everything the same way you did while in the office (assuming your VDI can do the things your corporate laptop could)
  • You can work from any device
  • You can take your hardware anywhere you wish
  • Your hardware stays cool and quiet no matter what you do

Cons:

  • You may have hard time connecting to the corporate VPN

This is how my final setup looks like. I don’t do anything on my road runner. Nothing. I just connect to the jump host with RDP + vSpatial + NoMachine and I forward my audio + video with VDO.Ninja. No matter if I code, process videos, record stuff, attend conference call (or many of them), remove my webcam background, or anything else – my road runner has constant and predictable load. I can also switch to any other road runner easily.

Double-thin-client architecture

Yet another trick you can apply is what I call “double-thin-client” architecture. Instead of booting up your road runner natively, you can use native boot and get yourself a portable SSD drive like Samsung T7.

This way, you can install your operating system once, and carry it with you wherever you go. You just plug the drive to the USB, boot the operating system from the drive, and you have your environment up and running. You can then replace your road runner with another machine easily. I actually did that many times. My Windows installation survived like 4 different laptops already. Best thing is, you can simply clone the drive to another one when you get a new SSD, so you can upgrade your hardware in place with no failures.

You can use similar approach for your jump host. Do not install things locally. Just boot from VHD and this way you can easily take backup of your dedicated host.

Other improvements

There are many more things you can consider for improving your remote work. Some ideas include Bluetooth devices (obviously) and VNC server installed on a road runner, so you can walk around your home and control the device from your mobile phone. This can be useful when you’re already dialed into a call and you don’t want to rejoin from another device. Simply take your Bluetooth headset with you to another room and watch the screen of your road runner from a mobile phone.

Another idea is automatically capturing meeting notes. Since you take calls on your jump host, you can run some notetaker AI application that will automatically capture whatever you need and send it forward.

You can use Firefox with Multi-Account Containers and Tree Style Tab. They really make your work way faster. Too bad it’s just for Firefox.

You may also want to use VirtuaWin to have many desktops. I find it much better to use than “tasks” in Windows, mostly thanks to keyboard shortcuts. And if you need to run applications that need to actively interact with your desktop (like Puppeteer or UIPath), just use Desktops.

Tricks for mobile phones

You can also work on your mobile phone. First, you can get a silicone keyboard to type easily. This is really great and you can actually work from your mobile phone. I once travelled for two days with my mobile phone only and I was still able to do coding and debugging.

You can dial in over GSM. This may simplify the way you take online meetings.

Next, you can run many clones of one application with Multiple Accounts. This way, you can separate your corporate stuff from your personal one on your road runner.

You can virtualize mobile phone inside a mobile phone. Just consider applications like VMOS or Virtual Master – Android Clone. You can try Limbo x86 PC Emulator.

You can turn your mobile phone into a mobile workstation with Samsung DeX.

Short note on Apple devices

I sometimes get asked why I don’t use Apple devices. If you watch my conference recording, you can see that I always use Windows and Android. This is surprising to some people because they consider me a “power user” and yet it seems like I don’t use Apple devices. Let me give you some reasons.

First, I do use them. I really like my Mac with silicon chip as it’s really great CPU + GPU in one box. However, I don’t use it as a road runner for three reasons:

  • It’s heavy. It’s over 25% heavier than my Windows road runner. While it’s not a big deal as it’s still relatively light, I just don’t want to carry additional kilograms
  • It is loud and hot. I don’t like that it gets so hot when I don’t do crazy things. This is especially irritating when working in bed
  • Most importantly, Macs do not support many screens easily. Right now, I have 5 displays on my desk (not counting the monitor one). When I travel, I often plug additional screens to my laptop. I just don’t accept that Macbook Air doesn’t handle 3 external screens

When it comes to iPhone, I have these reasons to prefer Android:

  • iPhone doesn’t record calls. This is a major obstacle for which there is no easy workaround
  • iPhone can’t run many instances of an app. I want to run them independently because I need to log in with multiple accounts. Some applications support that natively, but I need a generic solution
  • Foldable phone is really a game changer
  • I have some “sophisticated” applications running on my mobile phone, for instance web server and Node.js applications that automate things around text messages. I simply can’t have that on iPhone
  • Many things are not available for iPhone, like some of my VPNs, port forwarding, etc. Android just works

That’s it. I’ve been using iPhone for some time and it just doesn’t cut. For Macbook, I may use it as a road runner one day when it improves the hardware specification (sic!).

]]>
https://blog.adamfurmanek.pl/2024/04/24/availability-anywhere-part-26/feed/ 0
Types and Programming Languages Part 20 – Measuring how hard your work is https://blog.adamfurmanek.pl/2024/03/22/types-and-programming-languages-part-20/ https://blog.adamfurmanek.pl/2024/03/22/types-and-programming-languages-part-20/#respond Fri, 22 Mar 2024 20:53:02 +0000 https://blog.adamfurmanek.pl/?p=4991 Continue reading Types and Programming Languages Part 20 – Measuring how hard your work is]]>

This is the twentieth part of the Types and Programming Languages series. For your convenience you can find other parts in the table of contents in Part 1 — Do not return in finally

Sometimes you may ask yourself how hard you work. You could count the hours of work but we all know that some things are harder and some others are easier. One hour isn’t the same effort in different activities. Similarly, it’s easier to work when you’re not supervised and there is no time pressure. With deadlines ahead of you, the same amount of work now becomes harder and more stressful. Another factor is when you can do your work. Maybe you can work 24/7 and do it when you just feel like it, or maybe you need to stick to a strict schedule.

Being that said, there are many factors that affect “how hard” the work is. I was considering that recently and this is the formula I think works in my case. Let’s call this “Work Complexity Model”:

    \begin{gather*} C - \text{Cost of single context switch between activities} \\ A - \text{Set of activities} \\ S - \text{The size of particular increment} \\ I - \text{Number of increments in a given timeframe}\\ T - \text{Timeframe length} \\ E - \text{Final effort} \\ E = C^{card(A)} \sum_{a \in A} \frac{S \cdot I ^2} {T} \end{gather*}

Let’s say that at your work you need to do coding, doc writing, and mentoring. Therefore, your set of activities would have these three elements. You then need to asses the cost of context switch which is your personal coefficient. It doesn’t matter per se and do not compare it with others. You can use it to compare your effort in different months when you move between projects or tasks.

Next, you need to decide on the period, for instance a single quarter.

Next, for each activity you need to measure how many increments you have. If you need to deliver your work at the end of the sprint, then you would have six increments (two increments each month). If you need to deliver something every day, then you would have ninety increments.

You then need to measure the size of the increment. Obviously, this is very subjective and it’s up to you to define how hard a particular piece of work is. Technically, this should be the amount of energy (physical and mental) you spent on the task. Since it’s hard to measure, you can just count the number of hours dedicated to the task and then multiply that by how intense and frustrating the work was.

Finally, you need to include the length of the timeframe to do the work. If you can work asynchronously 24/7, then your timeframe would be the whole period. If you can do your work during work days 9-5, then it’s just these working hours.

Let’s say that you can do coding 24/7, same for doc writing, but the mentoring you can do only on Mondays 9-5. You need to deliver your coding artifacts every other week, your doc writing twice a week, and your mentoring every Monday. Therefore, this would be your complexity over 3 months.

    \begin{gather*} E = C^3 \cdot \left(\frac{S_1 \cdot 6^2} {2160} + \frac{S_2 \cdot 24^2} {2160} + \frac{S_3 \cdot 12^2} {48} \right) \end{gather*}

See that the formula has the following features:

  • It shows that context switching has some cost that scales non-linearly with the number of activities
  • Number of increments affects the result much more than the size of the increments. That’s because supervision tends to slow us much more
  • The length of the timeframe is also included in the formula

It’s up to you what your values for C and S are. The goal of this formula is not to give you some absolute scale. It’s much more to compare your different projects to have some numbers showing you how hard it was, as we know that our memory misleads us often.

]]>
https://blog.adamfurmanek.pl/2024/03/22/types-and-programming-languages-part-20/feed/ 0
Availability Anywhere Part 25 — Supercharge your VR experience https://blog.adamfurmanek.pl/2024/03/22/availability-anywhere-part-25/ https://blog.adamfurmanek.pl/2024/03/22/availability-anywhere-part-25/#respond Fri, 22 Mar 2024 20:15:30 +0000 https://blog.adamfurmanek.pl/?p=4988 Continue reading Availability Anywhere Part 25 — Supercharge your VR experience]]>

This is the twentieth fifth part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

Today we’re going to make our VR experience even better. This is continuation of the previous part.

As mentioned before, there are issues with ALT+TAB and WINDOWS key. I remapped CAPS LOCK to WINDOWS with PowerToys, but today we’re doing something better.

First, I was looking for a good full-size keyboard with touchpad that I could use with VR. The best thing I found is Perixx PERIBOARD-313. Touchpad is in some weird position but I got used to that. It has this irritating Fn key, lacks Right WINDOWS, and the space is quite short, but generally the keyboard is good enough.

However, it’s a wired keyboard. You can plug that into your VR headset and it works well. The keyboard has additional USB slots, so you can plug more devices to your VR this way. Still, we’d like to make it wireless. To do that, I’m using Bluetooth Adapter for Keyboard & Mouse (BT-500). This adapter has a couple of interesting features. First, it turns your wired keyboard or mouse into a wireless one. Second, it can remap keys on the fly, add macros, or have custom settings that you can switch between easily.

I’m using the adapter to remap Left WINDOWS to CAPS LOCK, and TAB to SCROLL LOCK. This is my setup:

map add l_com caps_lock
map add tab scroll_lock
save
reboot

Next, I remap CAPS LOCK to Left WINDOWS back using PowerToys. I do the same for SCROLL LOCK and TAB.

Finally, I use another adapter to turn any wired mouse into wireless one. You could go with Bluetooth mouse instead. With these things in place, I now have a “regular” wireless keyboard that I can use with VR. Even though it’s Android behind the scenes, all is just like a regular Windows laptop.

As a side note, I tried remapping keys to F13 and similar special keys. Unfortunately, Quest 3 doesn’t handle these properly and my remote machine I connect to over vSpatial doesn’t receive the keys.

]]>
https://blog.adamfurmanek.pl/2024/03/22/availability-anywhere-part-25/feed/ 0
Availability Anywhere Part 24 — Make RDP retain position of windows and stop moving them around https://blog.adamfurmanek.pl/2024/03/09/availability-anywhere-part-24/ https://blog.adamfurmanek.pl/2024/03/09/availability-anywhere-part-24/#respond Sat, 09 Mar 2024 08:53:42 +0000 https://blog.adamfurmanek.pl/?p=4969 Continue reading Availability Anywhere Part 24 — Make RDP retain position of windows and stop moving them around]]>

This is the twentieth fourth part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

Today we’re going to solve a problem of RDP session moving your windows around when you connect from multiple machines. Let’s see how it works.

Problem statement

First, a couple of assumptions what I’m trying to solve:

  • I’m using mstsc.exe to connect to the RDP server. mstsc.exe is the regular Windows RDP client (so called the “blue” client). I’m using the regular application, not the UWP one
  • I have two machines that I use to connect to the RDP server. These machines are Client1 and Client2. Both Clients have three screens attached. While I’m using physical screens, you’re free to use IddSampleDriver or vSpatial virtual screens. I think the same should apply to other solutions like spacedesk and similar. The same should apply to screens created by BetterDisplay (when you RDP from Mac to your Client1).
  • Screen1 is “at front”, Screen2 is “on the right”, Screen3 is “on the left”. The actual ordering doesn’t matter but I’ll refer to this later in this post
  • I use Client1 to connect to the RDP server and I open maximized Browser on Screen1 and maximized Notepad on Screen2. I then disconnect and connect to the RDP server from Client2. At this point, I want Browser to still be on Screen1 (“at front”) and Notepad on Screen2 (“on the right”).
  • Screen1 is my “main display” and it has the taskbar. I want the same in the RDP server (so taskbar should be on Screen1)

I’m going to use screenshots to explain my setup. They are a little trickier to let’s walk one by one.

My setup

First, my Client1 has five physical screens in total. However, I use Screen4 to duplicate my Screen1, and I use Screen5 to keep things outside of RDP. Therefore, I only want to use Screen1, Screen2, and Screen3 in the RDP session (and obviously Screen4 which is implicitly showing Screen1). I also have 5 additional virtual screens created with IddSampleDriver and 2 more virtual screens created with vSpatial. I don’t use these virtual screens (they are turned off).

This is what “Display” shows me which from now on I’ll call “Display Setup”:

So this is the mapping:

  • Screen1 (“at front”) is Display Setup Screen 1
  • Screen2 (“on the right”) is Display Setup Screen 12
  • Screen3 (“on the left”) is Display Setup Screen 11
  • Screen4 (duplicating Screen1) is Display Setup Screen 9
  • Screen5 (kept outside of RDP) is Display Setup Screen 10
  • Display Setup Screen 2-6 are IddSampleDriver virtual screens
  • Display Setup Screen 7-8 are vSpatial virtual screens

Now I want to use screens Screen1 + Screen2 + Screen3 in RDP session only. Scott Hanselman explains how to do it. We first need to list the screens with mstsc /l and this is what I get (which from now I’ll call “MSTSC Setup”):

So here comes the mapping:

  • Screen1 (“at front”) is MSTSC Setup Screen 44
  • Screen2 (“on the right”) is MSTSC Setup Screen 46
  • Screen3 (“on the left”) is MSTSC Setup Screen 47
  • Screen4 (duplicating Screen1) is MSTSC Setup Screen 44 (the same as Screen1)
  • Screen5 (kept outside of RDP) is MSTSC Setup Screen 45

It’s important to notice that numbers do not match between Display Setup and MSTSC Setup. This is very important and makes the whole trouble. Let’s now solve the problems.

Using only a subset of displays in the RDP session

This is actually easy. When you see Scott Hanselman’s blog, it mentions the parameter called selectedmonitors. You use it to specify the comma-separated-list of monitors that you want to use for the RDP session.

In my case, I need to use MSTSC Setup Screen 44, MSTSC Setup Screen 46, and MSTSC Setup Screen 47. So the parameter in my case is:

selectedmonitors:s:44,46,47

After connecting to RDP, this is what “Display” shows (which I’ll refer to as Display RDP Setup):

In textual form each line is screenId: width x height; (xBegin, yBegin, xEnd, yEnd):

44: 1920 x 1080; (0, 0, 1919, 1079)
45: 1080 x 1920; (363, 1080, 1442, 2999)
46: 2560 x 1440; (1920, 0, 4479, 1439)
47: 2560 x 1440; (-2560, 0, -1, 1439)

So, here is the mapping:

  • Screen1 (“at front”) is Display RDP Setup Screen 1
  • Screen2 (“on the right”) is Display RDP Setup Screen 2
  • Screen3 (“on the left”) is Display RDP Setup Screen 3

You can see that there are only 3 screens in the RDP session. Exactly as I wanted.

Controlling the main display in RDP

selectedmonitors parameter controls this. The main display is the first monitor from this list. So, if you have selectedmonitors:s:x,y,z, then the screen x is going to be the main display in RDP. This doesn’t need to match your main display on the host. In my case, my parameter is set to selectedmonitors:s:44,46,47, so the main display in RDP is going to be MSTSC Setup Screen 44.

Making RDP not move windows

This is the trickiest part. Remember that I open maximized Browser on Screen1 and maximized Notepad on Screen2. Based on our mappings above, the Browser is on Screen1 = Display Setup Screen 1 = Display RDP Setup Screen 1 = MSTSC Setup Screen 44. Similarly, Notepad is on Screen2 = Display Setup Screen 12 = Display RDP Setup Screen 2 = MSTSC Setup Screen 46.

Now, these are the things that I believe are correct. I found them empirically and they work for me across 4 different laptops, but your mileage my vary.

  • The order of screens in selectedmonitors parameter doesn’t matter (apart from choosing the “main display” explained in the previous section)
  • The numbers from Display Setup do not matter! The numbers from MSTSC Setup do not matter! The only thing that matters is the order of screens from MSTSC Setup
  • The resolutions of the screens do not matter! Windows remembers maximized windows positions not based on the geometry (like “the window’s top left corner is in pixel 0,0) but rather as “the window is maximized on Display RDP Setup Screen 1
  • You can’t control numbers from MSTSC Setup programmatically. You can only control them by physically moving your screens to different ports/connectors/docking stations/USB adapters, etc.
  • To get the windows not move, you need to have screen “passed to RDP” in the same order between Client1 and Client2 (between different clients)

That’s it. It may be a little bit cryptic, so let’s delve into how I think it works.

mstsc.exe uses some API to enumerate displays and then applies logic to merge windows or reorder them. Notice, that in my case Display Setup shows 12 screens while MSTSC Setup shows only 4. That’s because 7 virtual screens are turned off, and Screen4 duplicates Screen1.

Now, it seems that mstsc.exe enumerates the screens, and then for each screen checks if it was selected in selectedmonitors. If yes, then it registers the screen in the RDP session. So the code looks conceptually like:

foreach(screen in enumerateScreens()):
    if(screen.id in selectedmonitors) then
        registerScreenInRdpSession(screen)
    end
end

This explains why the order of screens in selectedmonitors doesn’t matter. Assuming that, this is what we need to get:

  • We want the Browser to still be presented on the Screen1 (“at front”). This screen was registered first in the loop above when connecting from Client1. Therefore, when Client2 enumerates the screens in mstsc.exe, Screen1 must be the first one that matches the if condition. In other words, Screen1 can be in whatever position according to MSTSC Setup and can have any number, but there must be no other selectedmonitors screen earlier in the list of mstsc.exe /l
  • We don’t care about resolutions. Screen1 on Client1 can have different resolution than Screen1 on Client2. The Browser will still be “maximized on the Display RDP Setup Screen 1
  • We want the Notepad to still be presented on the Screen2 (“on the right”). Same logic applies: mstsc.exe continues enumerating the screens and the second screen that gets picked must be the screen on the right

Now, how do we control the order of screens enumerated by mstsc.exe /l? The short answer is: we can’t do that programmatically. We need to physically change cables! What’s worse, this order breaks when you change settings like Duplicate screen or Extend desktop to this screen.

How to change cables physically? There are solutions:

  • Just plug cable to some other slot in your laptop/PC
  • Use docking station and change the order of cables (or move some monitors to your laptop and some through your docking station)
  • Use another docking station! Yes, you can have many of them (I actually have 2, that’s a long story)
  • Use USB adapters like i-tec USB Type C to DisplayPort Adapter, 4K Video, Supports 2 External Displays. I have two of them and they are great. Your mileage may vary, obviously
  • Try hacking mstsc.exe with things like API Monitor (I haven’t tried that but that seems plausible)

I tested this setup across four different laptops – one with physical screens, two with virtual screens by IddSampleDriver, one with virtual screens by vSpatial. In my case, windows are not moved anymore. I can literally switch from one computer to another with different screen resolutions but all the windows stay where they were. And yes, I had to switch my cables like crazy. In my case it was even worse, because Screen1 was attached directly (and so was enumerated first by Display Setup) but Screen4 was attached by an adapter (and was taking precedence in mstsc.exe /l). It works, though.

]]>
https://blog.adamfurmanek.pl/2024/03/09/availability-anywhere-part-24/feed/ 0
Availability Anywhere Part 23 — RDP over VR goggles with no PCVR https://blog.adamfurmanek.pl/2024/02/21/availability-anywhere-part-23/ https://blog.adamfurmanek.pl/2024/02/21/availability-anywhere-part-23/#respond Wed, 21 Feb 2024 21:10:54 +0000 https://blog.adamfurmanek.pl/?p=4961 Continue reading Availability Anywhere Part 23 — RDP over VR goggles with no PCVR]]>

This is the twentieth third part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

In this part we’re going to see how we can work remotely from VR goggles with no PCVR around. Effectively, we can work remotely with goggles and a bluetooth keyboard, nothing else.

My workstation

My home workstation is quite powerful. I have 5 physical screens which I use to display 4 different screens. I also use VirtuaWin to have many desktops. I also work remotely from a dedicated machine to which I connect using MS RDP (mstsc.exe).

My typical setup works like this:

  • My main physical screen is 24” with FullHD resolution. I duplicate this physical screen to another physical screen which is 10” and has touch capabilities. I use the touch to draw diagrams.
  • My two additional side screens are 32” with 4K resolution
  • My fifth screen is 10” big and I use it to display all the instant messengers I need (slacks, discords, meets etc). That’s basically a browser window that I configure to be visible on all of the desktops.
  • I have 11 desktops configured in VirtuaWin.
  • To connect remotely to my machine, I move to some other VirtuaWin desktop and use four physical screens (FullHD + 2x4K + touch-enabled screen) to open mstsc.exe.
  • When I need to connect to some other machine, I basically do CTRL+ALT+HOME to unfocus the current RDP, then I switch to another VirtuaWin desktop, and there I connect to the server. This way I can quickly switch between multiple machines I have

This setup works great for me. I can work remotely from any place around the planet. When I’m not at home, I can use my road runner laptop with two physical screens or my mobile phone to connect over RDP to the remote server. I do various things over RDP – I code, I write/read documents, I postprocess videos, I record videos (yes, I record them over RDP!), I browse the Internet. Typical office work.

I wanted to replicate this setup as much as possible with VR goggles. I managed to do that with Meta Quest 3 and vSpatial. Let’s see how I did that.

What we need

First, you need the goggles. I used Meta Quest 3 and they were quite good. I can’t complain on the image quality and I definitely can do all the stuff I need. The battery lasts for around 90-120 minutes which is okayish.

Next, you need to have a machine with many physical displays. That’s the most tricky part if you want to configure it entirely over RDP and use free version of vSpatial. I use two dedicated machines: one is Windows Server and the other is Mac. I first connect to Windows Server from my mobile phone over the regular RDP and log in as User1. At this point, I have a session in Windows Server that has just one display. Next, I use NoMachine to connect from Windows Server to my Mac. In my Mac, I create multiple physical screens using Better Display. Next, I connect from my Mac to my Windows Server and log in as User2 using the regular RDP application. At this point, I have a session in Windows Server that has multiple physical screens. All of that done entirely from my mobile phone, so I can do that when I’m travelling. While it works and allows for any setup of monitors, it’s probably good enough to just use 4 screens from paid vSpatial. Up to you.

Next, we need the vSpatial server. I install it in Windows Server for User2. I then log in to Windows Server using vSpatial application in Meta Quest 3 goggles. The vSpatial client correctly recognizes all 4 displays and now I have access to a regular Windows machine. I can install VirtuaWin and configure desktops the regular way. It all works just like on the screenshot below:

You can see one screen at the top and three screens in the middle. This is nearly the same setup as I have at home.

Finally, I just pair my bluetooth keyboard with touchpad and that’s it. CTRL+ALT+HOME works, so I can work the same way as at home.

Now, sky is the limit. Since I managed to RDP into a regular Windows machine, I can RDP anywhere I need, I can install any application I need, and I can do all of that using my goggles and the bluetooth keyboard only. I want to stress that there is no PCVR here. The Windows Machine with vSpatial server is completely virtualized and has no dedicated graphics card. Also, I don’t use powerful Internet connection. I just connect over regular WiFi. This works from mobile hotspot as well as it’s just RDP.

When it comes to online meetings, I can take them from my mobile phone. If I want to share the screen, I can just join the same meeting from my mobile phone and from my remote machine, and I can share the screen from Windows Server. However, you can also start browser on VR and join meeting like in Google Meet. The meeting will work even after you switch to vSpatial.

Quirks

There are quirks, obviously.

First, Meta Quest 3 seems to be based on Android. Bluetooth keyboards don’t work 100% properly with Android when you RDP to Windows. One issue is that the WIN key doesn’t work. Another, some shortcuts (like ALT+TAB) are captured by the host (goggles in this case).

To solve the lack of WIN key, I use PowerToys. I just remap CAPS LOCK to LEFT WIN on my Windows Server. You can also use AutoHotkey with the following script:

*CapsLock::
    Send {Blind}{LWin Down}
Return

*CapsLock up::
    Send {Blind}{LWin Up}
Return

The script rebinds CAPS LOCK to LEFT WIN. No matter which method you use, you can just use CAPS LOCK in all the shortcuts (like maximizing the window with LEFT WIN+ARROW UP which becomes CAPS LOCK+ARROW UP). While it’s a bit painful, it works. You need to install AutoHotkey in all the remote machines you connect to.

I didn’t solve the ALT+TAB issue with AutoHotkey. I solved it with PowerToys by remapping SCROLL LOCK to TAB. I think you could use AutoHotkey to remap it as well.

Second, vSpatial has a concept of active (focused) desktop. If you worked on one screen and want to click on another one, the mouse click changes the focus. You need to click again to do the actual click you want. This is not a big deal and I got used to it very fast.

Third, I think vSpatial is slightly slower than the regular RDP. I don’t find this as a problem, though, as it’s fast enough for me.

Fourth, the goggles don’t last for long (up to two hours). On the other hand, maybe it’s better for your eyes. You can also buy an additional strap with built-it powerbank.

Summary

It took me quite a while to figure out how to configure this setup. However, now I can truly work using goggles and no laptop around. That sounds like a great approach when traveling – imagine sitting at the airport and having multiple screens, just like at home. Now, the only issue is around the goggles. I hope one day they will be smaller, lighter, and will have stronger batteries. You can always plug them into powerbank if needed, though.

]]>
https://blog.adamfurmanek.pl/2024/02/21/availability-anywhere-part-23/feed/ 0
Availability Anywhere Part 22 — Customer Experience Improvement Program restarts https://blog.adamfurmanek.pl/2024/02/11/availability-anywhere-part-22/ https://blog.adamfurmanek.pl/2024/02/11/availability-anywhere-part-22/#respond Sun, 11 Feb 2024 10:10:10 +0000 https://blog.adamfurmanek.pl/?p=4943 Continue reading Availability Anywhere Part 22 — Customer Experience Improvement Program restarts]]>

This is the twentieth second part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

I was recently investigating a case of a computer restarting every Sunday at 3AM UTC. I couldn’t figure out what was going on and I suspected Customer Experience Improvement Program as I found the following event in event viewer:

User Logoff Notification for Customer Experience Improvement Program

- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
		<Provider Name="User32" Guid="{b0aa8734-56f7-41cc-b2f4-de228e98b946}" EventSourceName="User32" /> 
		<EventID Qualifiers="32768">1074</EventID> 
		<Version>0</Version> 
		<Level>4</Level> 
		<Task>0</Task> 
		<Opcode>0</Opcode> 
		<Keywords>0x8080000000000000</Keywords> 
		<TimeCreated SystemTime="2024-02-04T03:00:01.2906001Z" /> 
		<EventRecordID>404828</EventRecordID> 
		<Correlation /> 
		<Execution ProcessID="896" ThreadID="804" /> 
		<Channel>System</Channel> 
		<Computer>computerName</Computer> 
		<Security UserID="S-1-5-18" /> 
	</System>
- <EventData>
		<Data Name="param1">C:\Windows\system32\shutdown.EXE (computerName)</Data> 
		<Data Name="param2">computerName</Data> 
		<Data Name="param3">No title for this reason could be found</Data> 
		<Data Name="param4">0x800000ff</Data> 
		<Data Name="param5">restart</Data> 
		<Data Name="param6">System is scheduled to reboot in 10 minutes. Please save your work.</Data> 
		<Data Name="param7">NT AUTHORITY\SYSTEM</Data> 
	</EventData>
</Event>

I checked many solutions on the Internet and none of them helped. However, later I realized it was not due to Customer Experience Improvement Program. After carefully checking the event viewer, I found the following:

The process C:\Windows\system32\shutdown.EXE (computerName) has initiated the restart of computer computerName on behalf of user NT AUTHORITY\SYSTEM for the following reason: No title for this reason could be found
Reason Code: 0x800000ff
Shutdown Type: restart
Comment: System is scheduled to reboot in 10 minutes. Please save your work.

- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
		<Provider Name="Microsoft-Windows-Winlogon" Guid="{dbe9b383-7cf3-4331-91cc-a3cb16a3b538}" /> 
		<EventID>7002</EventID> 
		<Version>0</Version> 
		<Level>4</Level> 
		<Task>1102</Task> 
		<Opcode>0</Opcode> 
		<Keywords>0x2000200000000000</Keywords> 
		<TimeCreated SystemTime="2024-02-04T03:10:41.0539456Z" /> 
		<EventRecordID>404868</EventRecordID> 
		<Correlation /> 
		<Execution ProcessID="6028" ThreadID="6892" /> 
		<Channel>System</Channel> 
		<Computer>computerName</Computer> 
		<Security UserID="S-1-5-18" /> 
	</System>
- <EventData>
		<Data Name="TSId">2</Data> 
		<Data Name="UserSid">S-1-5-21-1801674531-515967899-839522115-19733726</Data> 
	</EventData>
</Event>

So it seems that something triggered the restart. I wasn’t sure what that was and I just solved it by cancelling the restart. Just run this batch script to cancel any shutdowns every 30 seconds:

:start
shutdown /a
timeout 30
goto start

It worked good enough.

]]>
https://blog.adamfurmanek.pl/2024/02/11/availability-anywhere-part-22/feed/ 0
ILP Part 106 — Golden waste https://blog.adamfurmanek.pl/2023/12/16/ilp-part-106/ https://blog.adamfurmanek.pl/2023/12/16/ilp-part-106/#respond Sat, 16 Dec 2023 07:51:05 +0000 https://blog.adamfurmanek.pl/?p=4927 Continue reading ILP Part 106 — Golden waste]]>

This is the one hundred and sixth part of the ILP series. For your convenience you can find other parts in the table of contents in Part 1 – Boolean algebra

Let’s solve Golden Waste (Złoty Odpad) riddle. We have a board with golden coins on it. One of the coins is a starting point and is numbered 1. We need to go along the blue lines and collect all the coins with the following rules:

  • we must collect a coin when we get to it
  • when collecting a coin, we can either continue straight or turn left or right; we can’t turn around (do U turn)

Let’s solve that with ILP. The idea is as follows:

  • We want to number coins from one to n (number of coins); we represent this number with a bitset (to make things faster)
  • each coin has two bitsets indicating which direction we entered this coin (up, right, bottom, left) and which direction we left
  • for each coin, we analyze all four directions and make sure that all coins on the path in that direction have either lower number (so they were collected earlier) or we entered the first coin with higher number from the correct direction

Let’s see the code:

public class Field {
	public IVariable[] DirectionIn {get;set;}
	public IVariable[] DirectionOut {get;set;}
	public IVariable[] Number {get;set;}
}

var board = new string[]{
	" X     X",
	"    X X ",
	"   XX X1",
	"X      X",
	"  XXXX  ",
	"X  XX   ",
	"    XX  ",
	"XXXX X  "
};

var numbersCount = board.SelectMany(line => line.Select(character => character)).Count(character => character != ' ');

var fields = board.Select(line => line.Select(f => f != ' ' ? new Field{
	DirectionIn = Enumerable.Range(0, 4).Select(i => solver.CreateAnonymous(Domain.BinaryInteger)).ToArray(),
	DirectionOut = Enumerable.Range(0, 4).Select(i => solver.CreateAnonymous(Domain.BinaryInteger)).ToArray(),
	Number = Enumerable.Range(0, numbersCount).Select(i => solver.CreateAnonymous(Domain.BinaryInteger)).ToArray()
} : null).ToArray()).ToArray();

for(int row=0;row<board.Length;++row){
	for(int column=0;column<board[0].Length;++column){
		if(board[row][column] != ' '){
			var thisField = fields[row][column];
			
			// If this is one, then select it
			if(board[row][column] == '1'){
				thisField.Number[0].Set<Equal>(solver.FromConstant(1));
			}
			
			// Exactly one number
			solver.Set<Equal>(solver.FromConstant(1), solver.Operation<Addition>(thisField.Number));
			
			// Cannot turn around
			for(int direction=0;direction<4;++direction){
				thisField.DirectionIn[direction].Operation<Addition>(thisField.DirectionOut[direction]).Set<LessOrEqual>(solver.FromConstant(1));
			}
			
			// Exactly one direction in
			solver.Set<Equal>(solver.FromConstant(1), solver.Operation<Addition>(thisField.DirectionIn));
			
			// Exactly one direction out
			solver.Set<Equal>(solver.FromConstant(1), solver.Operation<Addition>(thisField.DirectionOut));
			
			// Either this is last fields
			// Or there is some other field with value + 1  and input direction matching output direction
			// 0 going up, 1 going right, 2 going down, 3 going left
			var isNotLastField = thisField.Number[numbersCount-1].Operation<BinaryNegation>();
			
			Func<Field, IVariable> numberHash = f => solver.Operation<Addition>(
				Enumerable.Range(0, numbersCount).Select(p => solver.Operation<Multiplication>(f.Number[p], solver.FromConstant(p))).ToArray()
			);
			
			Func<Field, int, int, int, IVariable, IVariable> matchField = (localThis, r, c, directionIn, shouldCarryOn) => {
				if(board[r][c] != ' '){
					var thatField = fields[r][c];
					
					var isLowerNumber = solver.Operation<IsLessOrEqual>(
						numberHash(thatField),
						numberHash(localThis)
					);
					
					var isNextNumber = solver.Operation<IsEqual>(
						solver.Operation<Addition>(solver.FromConstant(1), numberHash(localThis)),
						numberHash(thatField)
					);
					
					var isDirectionInMatching = thatField.DirectionIn[directionIn];
					
					solver.Set<Equal>(
						solver.FromConstant(1),
						solver.Operation<MaterialImplication>(
							solver.Operation<Conjunction>(shouldCarryOn),
							solver.Operation<Disjunction>(
								isLowerNumber,
								solver.Operation<Conjunction>(isNextNumber, isDirectionInMatching)
							)
						)
					);
					
					return solver.Operation<Conjunction>(shouldCarryOn, isLowerNumber);
				}
				
				return shouldCarryOn;
			};
			
			IVariable shouldContinue;
			
			// Going up
			shouldContinue = thisField.DirectionOut[0].Operation<Conjunction>(isNotLastField);
			for(int row2 = row-1;row2>=0;--row2){
				shouldContinue = shouldContinue.Operation<Conjunction>(matchField(thisField, row2, column, 2, shouldContinue));
			}
			shouldContinue.Set<Equal>(solver.FromConstant(0));
			
			// Going down
			shouldContinue = thisField.DirectionOut[2].Operation<Conjunction>(isNotLastField);
			for(int row2 = row+1;row2<board.Length;++row2){
				shouldContinue = shouldContinue.Operation<Conjunction>(matchField(thisField, row2, column, 0, shouldContinue));
			}
			shouldContinue.Set<Equal>(solver.FromConstant(0));
			
			// Going left
			shouldContinue = thisField.DirectionOut[3].Operation<Conjunction>(isNotLastField);
			for(int column2=column-1;column2>=0;--column2){
				shouldContinue = shouldContinue.Operation<Conjunction>(matchField(thisField, row, column2, 1, shouldContinue));
			}
			shouldContinue.Set<Equal>(solver.FromConstant(0));
			
			// Going right
			shouldContinue = thisField.DirectionOut[1].Operation<Conjunction>(isNotLastField);
			for(int column2=column+1;column2<board[0].Length;++column2){
				shouldContinue = shouldContinue.Operation<Conjunction>(matchField(thisField, row, column2, 3, shouldContinue));
			}
			shouldContinue.Set<Equal>(solver.FromConstant(0));
		}
	}
}

// All numbers are different
for(int number=0;number<numbersCount;++number){
	solver.Operation<Addition>(fields.SelectMany(f => f).Where(f => f != null).Select(f => f.Number[number]).ToArray()).Set<Equal>(solver.FromConstant(1));
}

solver.AddGoal("goal", solver.FromConstant(0));
solver.Solve();

for(int row=0;row<board.Length;++row){
	for(int column=0;column<board.Length;++column){
		if(board[row][column] != ' '){
			var thisField = fields[row][column];
			
			for(int number=0;number<numbersCount;++number){
				if(solver.GetValue(thisField.Number[number]) > 0){
					Console.Write((number < 9 ? " " : "") + (number + 1) + " ");
				}
			}
		}else{
			Console.Write("   ");
		}
	}
	Console.WriteLine();
}

First, we define our coin class (lines 1-5). Next, we define a starting board (lines 7-16). We also count the number of coins (18) and then initialize variables (20-24).

Next, we iterate over all fields (26) and look for coins (28). We hardcode the starting point (31-34). Next, we make sure that the bitset indicating the number has exactly one value (36-37), that we can’t exit the coin to go back (to the U-turn) (39-42), and that there is exactly one input direction and output direction (44-48).

Now, we encode the main part. We first store a variable indicating whether this is the last coin to collect (53). We define a helper variable that will decode number bitset into a single integer so we can compare them easily (55-57). Finally, we encode the main function that builds the path.

The idea is: we start from some coin localThis and we check other fields along some straight line. When we spot another coin, we calculate if it has lower number (and was collected earlier) (63), or is exactly next to be collected (68-71), and that we entered from the right direction (73). We then enforce this with material implication (75-84). Finally, we return the flag indicating whether we should continue looking along this path (lines 86, 89).

We then use this function in four directions: (94-99, 101-106, 108-113, 115-120). Finally, we just make sure that all coins have different numbers (125-128) and solve the problem. Output:

Tried aggregator 3 times.
MIP Presolve eliminated 1270 rows and 609 columns.
MIP Presolve modified 33469 coefficients.
Aggregator did 895 substitutions.
Reduced MIP has 1886 rows, 1454 columns, and 32267 nonzeros.
Reduced MIP has 1454 binaries, 0 generals, 0 SOSs, and 0 indicators.
Presolve time = 0.06 sec. (89.01 ticks)
Probing time = 0.00 sec. (4.29 ticks)
Tried aggregator 1 time.
Reduced MIP has 1886 rows, 1454 columns, and 32267 nonzeros.
Reduced MIP has 1454 binaries, 0 generals, 0 SOSs, and 0 indicators.
Presolve time = 0.02 sec. (11.67 ticks)
Probing time = 0.00 sec. (4.28 ticks)
Clique table members: 2759.
MIP emphasis: balance optimality and feasibility.
MIP search method: dynamic search.
Parallel mode: deterministic, using up to 12 threads.
Root relaxation solution time = 0.05 sec. (52.10 ticks)

        Nodes                                         Cuts/
   Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap

      0     0        0.0000   508                      0.0000      874
      0     0        0.0000   481                   Cuts: 238     1192
      0     0        0.0000   580                   Cuts: 410     2048
      0     0        0.0000   532                   Cuts: 192     2580
      0     0        0.0000   660                   Cuts: 498     3396
      0     2        0.0000   263                      0.0000     3400
Elapsed time = 2.36 sec. (2430.37 ticks, tree = 0.01 MB, solutions = 0)
     25    27        0.0000   372                      0.0000     9792
    182   146        0.0000   480                      0.0000    34215
    360   218        0.0000   359                      0.0000    59248
    473   275        0.0000   382                      0.0000    86080
    630   392        0.0000   392                      0.0000   102841
    961   441    infeasible                            0.0000   127669
   1029   455    infeasible                            0.0000   146053
   1149   479        0.0000   408                      0.0000   163289
   1216   540        0.0000   440                      0.0000   176188
   1551   579    infeasible                            0.0000   232730
Elapsed time = 5.97 sec. (5824.01 ticks, tree = 0.56 MB, solutions = 0)
   1690   585    infeasible                            0.0000   289363
   1805   601        0.0000   515                      0.0000   325263
   1983   602        0.0000   477                      0.0000   373383
   2124   600        0.0000   455                      0.0000   431960
   2316   589    infeasible                            0.0000   490860
   2546   600        0.0000   570                      0.0000   544653
   2586   598        0.0000   562                      0.0000   584681
   2750   632    infeasible                            0.0000   643752
   2914   652    infeasible                            0.0000   681857
   3119   652        0.0000   679                      0.0000   725066
Elapsed time = 22.92 sec. (18464.39 ticks, tree = 1.39 MB, solutions = 0)
   3251   658        0.0000   695                      0.0000   811097
   3287   656    infeasible                            0.0000   849022
   3404   681        0.0000   528                      0.0000   907778
   3484   695    infeasible                            0.0000   930758
   3586   721        0.0000   648                      0.0000   954548
   3874   774        0.0000   588                      0.0000  1014376
   4009   807        0.0000   444                      0.0000  1049944
   4065   821        0.0000   502                      0.0000  1069917
   4327   865        0.0000   407                      0.0000  1144971
   4367   877        0.0000   521                      0.0000  1160531
Elapsed time = 39.03 sec. (29642.77 ticks, tree = 1.74 MB, solutions = 0)
   4472   910        0.0000   417                      0.0000  1190811
   4763   909    infeasible                            0.0000  1275674
   4797   921        0.0000   475                      0.0000  1295329
   4821   935        0.0000   481                      0.0000  1312840
   4857   949        0.0000   508                      0.0000  1332501
   4918   960        0.0000   520                      0.0000  1392387
   5005   997        0.0000   455                      0.0000  1432805
   5072  1020        0.0000   594                      0.0000  1448193
   5160  1040        0.0000   321                      0.0000  1479499
   5246  1048        0.0000   398                      0.0000  1504146
Elapsed time = 54.63 sec. (42147.18 ticks, tree = 2.13 MB, solutions = 0)
   5426  1124        0.0000   423                      0.0000  1544836
   5711  1181    infeasible                            0.0000  1604289
   5787  1158    infeasible                            0.0000  1631589
   5850  1169        0.0000   595                      0.0000  1655718
   5966  1183        0.0000   596                      0.0000  1695224
   6145  1204        0.0000   611                      0.0000  1770821
   6253  1242        0.0000   595                      0.0000  1814478
   6279  1244    infeasible                            0.0000  1843727
   6318  1247    infeasible                            0.0000  1878957
   6342  1249    infeasible                            0.0000  1894346
Elapsed time = 70.39 sec. (54490.95 ticks, tree = 3.57 MB, solutions = 0)
   6453  1240        0.0000   653                      0.0000  1946631
   6764  1281        0.0000   556                      0.0000  2047656
   6804  1283    infeasible                            0.0000  2063706
   6864  1299    infeasible                            0.0000  2080575
   7032  1340        0.0000   404                      0.0000  2123133
   7164  1346        0.0000   386                      0.0000  2158194
   7320  1336        0.0000   389                      0.0000  2205352
   7598  1302        0.0000   492                      0.0000  2275263
   7824  1294        0.0000   386                     -0.0000  2350430
   7831  1300        0.0000   442                     -0.0000  2352517
Elapsed time = 93.77 sec. (77852.80 ticks, tree = 58.94 MB, solutions = 0)
   7850  1311        0.0000   404                     -0.0000  2355298
   7923   969        0.0000   442                     -0.0000  2366582
   8187   689        0.0000   402                     -0.0000  2400804
   8559   412        0.0000   318                     -0.0000  2435661
   8948   389    infeasible                           -0.0000  2470993
   9576   476        0.0000   330                     -0.0000  2520604
  10159   477        0.0000   334                     -0.0000  2579870
  10555   518        0.0000   303                     -0.0000  2618541
  10930   518        0.0000   303                     -0.0000  2664466
  11282   519        0.0000   418                     -0.0000  2711235
Elapsed time = 103.23 sec. (87766.62 ticks, tree = 1.00 MB, solutions = 0)
  11651   566        0.0000   366                     -0.0000  2758926
  11693   565        0.0000   380                     -0.0000  2764222
  12058   290        0.0000   402                     -0.0000  2801170
  12633   230    infeasible                           -0.0000  2865597
  13570   230        0.0000   239                     -0.0000  2948118
  14733   263    infeasible                           -0.0000  3061474
  15820   202        0.0000   287                     -0.0000  3169807
  17003   275        0.0000   300                     -0.0000  3281567
  17989   304        0.0000   216                     -0.0000  3379704
  19041   267        0.0000   359                     -0.0000  3481728
Elapsed time = 116.88 sec. (103305.54 ticks, tree = 0.03 MB, solutions = 0)
  20430   274    infeasible                           -0.0000  3627297
  21450   423        0.0000   302                     -0.0000  3711606
  23285   375        0.0000   276                     -0.0000  3861744
  24549   281    infeasible                           -0.0000  3979235
  26136   435        0.0000   218                     -0.0000  4127636
  27561   391        0.0000   288                     -0.0000  4249779
  29100   424        0.0000   240                     -0.0000  4376829
  30874   439        0.0000   277                     -0.0000  4524167
  32176   415        0.0000   317                     -0.0000  4638570
  33981   443        0.0000   303                     -0.0000  4798347
Elapsed time = 128.17 sec. (112887.60 ticks, tree = 0.05 MB, solutions = 0)
  35594   351        0.0000   374                     -0.0000  4933169
  36777   478        0.0000   341                     -0.0000  5045144
  38466   458        0.0000   241                     -0.0000  5209098
  39471   471        0.0000   314                     -0.0000  5311307
  41132   528        0.0000   166                     -0.0000  5465492
  42639   520    infeasible                           -0.0000  5596801
  44371   393    infeasible                           -0.0000  5747683
  45828   546        0.0000   318                     -0.0000  5889722
  47025   530        0.0000   263                     -0.0000  6007087
  52571   586    infeasible                           -0.0000  6553399
Elapsed time = 142.38 sec. (125333.77 ticks, tree = 0.08 MB, solutions = 0)
  58950   670        0.0000   353                     -0.0000  7130024
  64631   643        0.0000   281                     -0.0000  7646642
  69621   603    infeasible                           -0.0000  8169867
  74350   602        0.0000   264                     -0.0000  8669095
  80366   715        0.0000   176                     -0.0000  9230184
  85904   475        0.0000   319                     -0.0000  9771470
  91692   502        0.0000   316                     -0.0000 10317244
  97439   608        0.0000   262                     -0.0000 10808574
 103409   594        0.0000   155                     -0.0000 11361920
 109112   645    infeasible                           -0.0000 11891245
Elapsed time = 186.53 sec. (163579.26 ticks, tree = 0.06 MB, solutions = 0)
 114282   994    infeasible                           -0.0000 12422478
 119356   803    infeasible                           -0.0000 12941160
 125898   915        0.0000   273                     -0.0000 13509013
 132155   881        0.0000   262                     -0.0000 14064545
 137866   886    infeasible                           -0.0000 14590510
 143707   914        0.0000   288                     -0.0000 15177172
 149235   807        0.0000   285                     -0.0000 15701530
 154911   675        0.0000   178                     -0.0000 16241618
 161882   821        0.0000   201                     -0.0000 16817652
 169771   823        0.0000   294                     -0.0000 17381212
Elapsed time = 245.55 sec. (201783.67 ticks, tree = 0.09 MB, solutions = 0)
 177104   765        0.0000   153                     -0.0000 17960080
 184942   821        0.0000   317                     -0.0000 18569902
 192554   749        0.0000   260                     -0.0000 19131939
 198295   787        0.0000   244                     -0.0000 19644341
 204161   727        0.0000   203                     -0.0000 20187350
 210163   575        0.0000   188                     -0.0000 20746953
 217540   898        0.0000   223                     -0.0000 21304646
 224070   780    infeasible                           -0.0000 21843454
 230533   820        0.0000   263                     -0.0000 22406991
 236733   779        0.0000   272                     -0.0000 22960102
Elapsed time = 300.91 sec. (240013.77 ticks, tree = 0.07 MB, solutions = 0)
 242658   745    infeasible                           -0.0000 23514500
 247652   787        0.0000   344                     -0.0000 24030942
 252764   942    infeasible                           -0.0000 24564913
 258233   961        0.0000   325                     -0.0000 25099913
 263573   874        0.0000   322                     -0.0000 25638923
 268963   864        0.0000   284                     -0.0000 26164593
 273168   887        0.0000   278                     -0.0000 26658456
 277901   886        0.0000   210                     -0.0000 27176495
 283861   956        0.0000   313                     -0.0000 27737094
 289071  1070    infeasible                           -0.0000 28251655
Elapsed time = 344.66 sec. (278234.91 ticks, tree = 0.14 MB, solutions = 0)
 293946  1036        0.0000   295                     -0.0000 28774451
 298674  1027        0.0000   270                     -0.0000 29314425
 304035   901        0.0000   291                     -0.0000 29855358
 308755  1004        0.0000   260                     -0.0000 30377899
 313952  1082        0.0000   182                     -0.0000 30884392
 319647  1011    infeasible                           -0.0000 31447232
 325025   962        0.0000   368                     -0.0000 31993586
 331144  1152    infeasible                           -0.0000 32537413
 336554  1085        0.0000   293                     -0.0000 33082843
 342745  1060        0.0000   310                     -0.0000 33644741
Elapsed time = 399.03 sec. (316477.28 ticks, tree = 0.09 MB, solutions = 0)
 348043  1070        0.0000   325                     -0.0000 34195498
 353051  1083    infeasible                           -0.0000 34733741
 357706  1092        0.0000   204                     -0.0000 35234117
 362868  1098        0.0000   396                     -0.0000 35793118
 367279  1060        0.0000   250                     -0.0000 36308791
 372233  1152        0.0000   240                     -0.0000 36828052
 377434  1040        0.0000   218                     -0.0000 37350915
 382616  1201        0.0000   283                     -0.0000 37889699
 387312  1104        0.0000   321                     -0.0000 38398280
 392225  1292        0.0000   250                     -0.0000 38920991
Elapsed time = 447.94 sec. (354693.45 ticks, tree = 0.12 MB, solutions = 0)
 397318  1139        0.0000   375                     -0.0000 39441204
 402948  1109        0.0000   301                     -0.0000 39988911
 408043  1211        0.0000   325                     -0.0000 40527949
 413033  1266    infeasible                           -0.0000 41021940
 418087  1259        0.0000   336                     -0.0000 41533403
 422935  1164        0.0000   238                     -0.0000 42063377
 427481  1148        0.0000   288                     -0.0000 42573987
 432508  1157        0.0000   310                     -0.0000 43081190
 437531  1104        0.0000   393                     -0.0000 43621012
 442498  1095    infeasible                           -0.0000 44152646
Elapsed time = 493.66 sec. (392931.47 ticks, tree = 0.11 MB, solutions = 0)
 447235  1243        0.0000   367                     -0.0000 44673781
 454167  1167        0.0000   364                     -0.0000 45252390
 459563  1358        0.0000   258                     -0.0000 45779187
 465352  1280        0.0000   276                     -0.0000 46357473
 470467  1164    infeasible                           -0.0000 46875991
 475439  1234        0.0000   351                     -0.0000 47413722
 480265  1176        0.0000   341                     -0.0000 47920454
 485190  1296        0.0000   308                     -0.0000 48445537
 489439  1203        0.0000   275                     -0.0000 48969330
 494417  1212    infeasible                           -0.0000 49473099
Elapsed time = 543.44 sec. (431141.50 ticks, tree = 0.11 MB, solutions = 0)
 499878  1134        0.0000   212                     -0.0000 50001890
 505210  1150        0.0000   348                     -0.0000 50541821
 510703  1177        0.0000   288                     -0.0000 51085904
 516296  1191        0.0000   315                     -0.0000 51627983
 522136  1340        0.0000   275                     -0.0000 52196564
 527818  1254    infeasible                           -0.0000 52747921
 533847  1184    infeasible                           -0.0000 53277168
 539496  1225        0.0000   267                     -0.0000 53838500
 545609  1258        0.0000   324                     -0.0000 54438657
 550950  1279        0.0000   239                     -0.0000 54978252
Elapsed time = 608.14 sec. (469350.48 ticks, tree = 0.11 MB, solutions = 0)
 556262  1247        0.0000   248                     -0.0000 55503690
 562223  1376        0.0000   353                     -0.0000 56086832
 567933  1384        0.0000   303                     -0.0000 56644010
 573375  1411        0.0000   201                     -0.0000 57189815
 578931  1373    infeasible                           -0.0000 57706883
 584688  1302        0.0000   337                     -0.0000 58273497
 590210  1307    infeasible                           -0.0000 58818386
 596342  1262        0.0000   400                     -0.0000 59389073
 601521  1501        0.0000   261                     -0.0000 59915960
 607666  1618        0.0000   316                     -0.0000 60463241
Elapsed time = 655.89 sec. (507553.95 ticks, tree = 0.14 MB, solutions = 0)
 613534  1464    infeasible                           -0.0000 61020365
 619827  1346    infeasible                           -0.0000 61571663
 625674  1185        0.0000   337                     -0.0000 62134994
 630902  1230    infeasible                           -0.0000 62649879
 636152  1269        0.0000   267                     -0.0000 63170593
 641404  1232    infeasible                           -0.0000 63717245
 647098  1131        0.0000   277                     -0.0000 64283791
 651991  1146        0.0000   280                     -0.0000 64792110
 657625  1102        0.0000   336                     -0.0000 65339290
 662514  1143        0.0000   288                     -0.0000 65886969
Elapsed time = 701.64 sec. (545809.44 ticks, tree = 0.10 MB, solutions = 0)
 667761   982    infeasible                           -0.0000 66435420
 672993   997    infeasible                           -0.0000 66975538
 678105   878        0.0000   333                     -0.0000 67497233
 684034   904        0.0000   243                     -0.0000 68064925
 688856  1029    infeasible                           -0.0000 68561991
 694528   996        0.0000   357                     -0.0000 69108002
 699668   949        0.0000   315                     -0.0000 69630518
 704198   991    infeasible                           -0.0000 70161775
 708775  1017        0.0000   359                     -0.0000 70646954
 713778  1124        0.0000   319                     -0.0000 71197164
Elapsed time = 753.45 sec. (584025.21 ticks, tree = 0.10 MB, solutions = 0)
 718098  1053    infeasible                           -0.0000 71697499
 722978  1157        0.0000   354                     -0.0000 72209736
 727746  1094    infeasible                           -0.0000 72707109
 732735  1208        0.0000   321                     -0.0000 73253307
 737382  1158        0.0000   262                     -0.0000 73777309
 741743  1186        0.0000   314                     -0.0000 74282073
 746089  1111        0.0000   231                     -0.0000 74770022
 750690  1071        0.0000   333                     -0.0000 75294136
 756570  1056        0.0000   183                     -0.0000 75849757
 761838   990    infeasible                           -0.0000 76384511
Elapsed time = 806.95 sec. (622255.57 ticks, tree = 0.09 MB, solutions = 0)
 766503  1047        0.0000   199                     -0.0000 76884595
 772155   973        0.0000   357                     -0.0000 77445968
 777804   905        0.0000   307                     -0.0000 78018695
 782798  1022        0.0000   255                     -0.0000 78543623
 787886   915        0.0000   354                     -0.0000 79080195
 792781   837        0.0000   207                     -0.0000 79607676
 798278   812        0.0000   288                     -0.0000 80149715
 804008   877        0.0000   352                     -0.0000 80674521
 810292   950        0.0000   221                     -0.0000 81234497
 816081  1096        0.0000   248                     -0.0000 81761078
Elapsed time = 864.78 sec. (660493.24 ticks, tree = 0.09 MB, solutions = 0)
 821608   981        0.0000   339                     -0.0000 82343536
 826874   806        0.0000   328                     -0.0000 82820634
 832016   757        0.0000   328                     -0.0000 83327447
 837482   787        0.0000   308                     -0.0000 83869680
 842139   731        0.0000   323                     -0.0000 84381411
 847060   783    infeasible                           -0.0000 84938335
 851915   664        0.0000   290                     -0.0000 85467634
 857317   674        0.0000   237                     -0.0000 85996304
 862563   625    infeasible                           -0.0000 86529643
 868267   601        0.0000   337                     -0.0000 87056256
Elapsed time = 919.34 sec. (698703.51 ticks, tree = 0.06 MB, solutions = 0)
 874538   564        0.0000   289                     -0.0000 87647117
 880183   486    infeasible                           -0.0000 88177720
 885096   474        0.0000   393                     -0.0000 88714039
 890118   478        0.0000   278                     -0.0000 89215217
 894929   452    infeasible                           -0.0000 89745753
 899775   557        0.0000   289                     -0.0000 90248578
 905044   588        0.0000   284                     -0.0000 90809349
 909687   539        0.0000   291                     -0.0000 91299572
 914621   535        0.0000   372                     -0.0000 91835448
 919578   534        0.0000   259                     -0.0000 92373542
Elapsed time = 979.02 sec. (736908.26 ticks, tree = 0.05 MB, solutions = 0)
 924284   528    infeasible                           -0.0000 92897665
 928973   614        0.0000   284                     -0.0000 93422898
 933775   525        0.0000   303                     -0.0000 93952475
 938738   533        0.0000   248                     -0.0000 94478367
 943372   431        0.0000   174                     -0.0000 94995204
 948523   447        0.0000   334                     -0.0000 95503630
 953489   585        0.0000   402                     -0.0000 96027542
 958414   528        0.0000   374                     -0.0000 96555477
 962500   423        0.0000   353                     -0.0000 97053906
 967184   482        0.0000   268                     -0.0000 97556109
Elapsed time = 1033.42 sec. (775159.19 ticks, tree = 0.05 MB, solutions = 0)
 971887   500        0.0000   316                     -0.0000 98087834
 975986   487        0.0000   300                     -0.0000 98569907
 980549   469    infeasible                           -0.0000 99091590
 985736   427    infeasible                           -0.0000 99606175
 990496   452    infeasible                           -0.0000 1.00e+008
 996024   427        0.0000   367                     -0.0000 1.01e+008
 1001487   326        0.0000   399                     -0.0000 1.01e+008
 1006410   393        0.0000   338                     -0.0000 1.02e+008
 1010756   381        0.0000   340                     -0.0000 1.02e+008
 1015858   393        0.0000   400                     -0.0000 1.03e+008
Elapsed time = 1085.41 sec. (813376.28 ticks, tree = 0.05 MB, solutions = 0)
 1021091   437        0.0000   279                     -0.0000 1.03e+008
 1027315   368        0.0000   253                     -0.0000 1.04e+008
 1032859   283        0.0000   261                     -0.0000 1.04e+008
 1037989   506        0.0000   315                     -0.0000 1.05e+008
 1043080   291        0.0000   329                     -0.0000 1.05e+008
 1048499   481        0.0000   238                     -0.0000 1.06e+008
 1054622   474    infeasible                           -0.0000 1.06e+008
 1059697   488    infeasible                           -0.0000 1.07e+008
 1065295   516        0.0000   215                     -0.0000 1.07e+008
 1070060   537        0.0000   293                     -0.0000 1.08e+008
Elapsed time = 1150.30 sec. (851586.95 ticks, tree = 0.16 MB, solutions = 0)
 1075283   516        0.0000   255                     -0.0000 1.08e+008
 1080812   392        0.0000   336                     -0.0000 1.09e+008
 1085879   298        0.0000   336                     -0.0000 1.10e+008
 1091175   558    infeasible                           -0.0000 1.10e+008
 1096144   557    infeasible                           -0.0000 1.11e+008
 1100783   505        0.0000   271                     -0.0000 1.11e+008
 1106516   471        0.0000   305                     -0.0000 1.12e+008
 1111268   484        0.0000   365                     -0.0000 1.12e+008
 1116540   569        0.0000   303                     -0.0000 1.13e+008
 1121296   601        0.0000   299                     -0.0000 1.13e+008
Elapsed time = 1210.66 sec. (889845.90 ticks, tree = 0.06 MB, solutions = 0)
 1125401   527    infeasible                           -0.0000 1.14e+008
 1129996   481        0.0000   330                     -0.0000 1.14e+008
 1133934   408        0.0000   305                     -0.0000 1.15e+008
 1138889   437    infeasible                           -0.0000 1.15e+008
 1143893   449        0.0000   220                     -0.0000 1.16e+008
 1149074   467        0.0000   364                     -0.0000 1.16e+008
 1153624   329        0.0000   352                     -0.0000 1.17e+008
 1158969   491    infeasible                           -0.0000 1.17e+008
 1163791   431    infeasible                           -0.0000 1.18e+008
 1167484   332        0.0000   318                     -0.0000 1.18e+008
Elapsed time = 1264.00 sec. (928040.04 ticks, tree = 0.26 MB, solutions = 0)
 1171268   317    infeasible                           -0.0000 1.19e+008
 1175984   329        0.0000   287                     -0.0000 1.19e+008
 1180925   323        0.0000   293                     -0.0000 1.20e+008
 1185958   242        0.0000   415                     -0.0000 1.20e+008
 1191580   223    infeasible                           -0.0000 1.21e+008
*1194993   186      integral     0        0.0000       -0.0000 1.21e+008    0.00%

Root node processing (before b&c):
  Real time             =    2.33 sec. (2407.88 ticks)
Parallel b&c, 12 threads:
  Real time             = 1294.31 sec. (947046.19 ticks)
  Sync time (average)   =  178.58 sec.
  Wait time (average)   =    0.08 sec.
                          ------------
Total (root+branch&cut) = 1296.64 sec. (949454.07 ticks)
   17                18
             4     3
          6  5     2  1
20                   19
      14  7 13 12
21        8  9
            10 11
22 16 15 23    24

]]>
https://blog.adamfurmanek.pl/2023/12/16/ilp-part-106/feed/ 0
Availability Anywhere Part 21 — Fixed mstsc.exe broken UI https://blog.adamfurmanek.pl/2023/12/16/availability-anywhere-part-21/ https://blog.adamfurmanek.pl/2023/12/16/availability-anywhere-part-21/#respond Sat, 16 Dec 2023 07:32:14 +0000 https://blog.adamfurmanek.pl/?p=4922 Continue reading Availability Anywhere Part 21 — Fixed mstsc.exe broken UI]]>

This is the twentieth first part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

I recently installed a set of November 23′ updates for Windows 10 and mstsc.exe disappeared! That was definitely unexpected and I couldn’t find a solution on the Internet. I decided to copy mstsc.exe from some other machine and it wasn’t as simple as possible.

TL;DR; Those are the files that you may need:

  • C:\Windows\System32\mstsc.exe
  • C:\Windows\System32\mstscax.dll
  • C:\Windows\System32\en-US\mstsc.exe.mui
  • C:\Windows\System32\en-US\mstscax.dll.mui
  • C:\Windows\System32\wbem\en-US\mstsc.mfl
  • C:\Windows\System32\wbem\en-US\mstscax.mfl
  • C:\Windows\SystemResources\mstsc.exe.mun
  • C:\Windows\SystemResources\mstscax.dll.mun

You may need similar files from C:\Windows\SysWOW64 or for different locales like pl-PL.

When you skip files in SystemResources, then your mstsc.exe will look like this (attaching this image so Google can index it and people have easier time finding things like “mstsc.exe no options” or “mstsc.exe UI broken” or “mstsc.exe no settings” or “mstsc.exe no show options”):

Copying files into C:\Windows\SystemResources is not as simple as it may seem. That directory is owned by TrustedInstaller and even SYSTEM has no permissions to write there. You can run shell like in this StackOverflow answer which I copy here just in case (run this in PowerShell started as Administrator):

Install-Module -Name NtObjectManager
Start-Service -Name TrustedInstaller
$parent = Get-NtProcess -ServiceName TrustedInstaller
$proc = New-Win32Process cmd.exe -CreationFlags NewConsole -ParentProcess $parent

Even though whoami shows nt authority\system, this effectively runs as TrustedInstaller.

If you wonder how I found out about these files, I used ProcessMonitor and looked for CreateFile operation that failed.

]]>
https://blog.adamfurmanek.pl/2023/12/16/availability-anywhere-part-21/feed/ 0
Types and Programming Languages Part 19 – Senior or Expert or what? https://blog.adamfurmanek.pl/2023/02/24/types-and-programming-languages-part-19/ https://blog.adamfurmanek.pl/2023/02/24/types-and-programming-languages-part-19/#respond Fri, 24 Feb 2023 09:00:06 +0000 https://blog.adamfurmanek.pl/?p=4938 Continue reading Types and Programming Languages Part 19 – Senior or Expert or what?]]>

This is the nineteenth part of the Types and Programming Languages series. For your convenience you can find other parts in the table of contents in Part 1 — Do not return in finally

Who is a senior software engineer? How do we become one? Does it matter at all? Let’s see some dimensions we can discuss when “assessing” someone’s skills.

Maturity

What does it mean to be mature? Many people think it’s important to point bad sides of solutions. No matter if we’re talking about the software engineering, social insurance, taxes, or whatever other thing that people like to discuss. However, that’s not the point. While it may seem that good engineers (professionals in general) can find drawbacks of solutions and identify where they fail, that’s not what we need. Let’s see some levels how people progress and what’s important.

First level – just complaining

This is easy and we do that so often. “Taxes are too high and they should be lower”. “This code is just broken and we should fix it”. Both of these statements may be 100% correct and yet they are completely non-actionable. It’s easy to “just complain” but it’s hard to explain why things are wrong and how to make them right.

When it comes to software engineering, we can give these statements literally about anything. We very often do that when criticizing some programming language, technology, library, framework, programming paradigm, particular solution, or anything else. This is just a trash talk that brings nothing.

Second level – being good at complaining

This is much harder. When someone achieves this level, they can easily point drawbacks of some particular solution and explain what they mean. What’s more, they are very often correct and make really good calls. They can for instance explain why a particular GC implementation won’t work here, why such and such library is not good enough, why some code is unreadable, or why some solution is slow. They can easily find edge cases where the solution will fail or explain why designs won’t work.

However, this is still not what we need. We need to understand that reality is never perfect. We can’t make our code (taxes, social insurance, health agency, whatever else) perfect. We can’t avoid all the problems. It’s not enough to just show where things won’t work and that they have drawbacks because everything have drawbacks. Interestingly enough, many software architects are on this level because they can always tell you “what not to do” but they can never give a good solution. Similarly, politicians are great at criticizing others but very rarely give solutions.

Another example is when software engineers can provide many options but can never decide which one to choose because all of them have some issues. Later, they typically push the decision on someone else (client, product owner, software architect) and then can complain that a wrong decision has been made.

Third level – giving solutions

Finally, people realize that the world is not perfect, and yet we need to deal with it somehow. They can give ideas (and even criticize them) and they can make decisions what to do. Even though they understand things will not be perfect, they can take the risk or even consciously pick something that they know will not work perfectly.

It’s important to understand that once one makes a choice, they will always be criticized. Always. That’s because one cannot pick a perfect solution because it doesn’t exist. Therefore, if you want to become mature enough and achieve this level, you need to accept that you will be always criticized. No matter if you’re a software engineer, software architect, or a politician. You’ll always get criticized, no matter what you do.

Seniority

Now, we can discuss what it means to be a “senior”. It’s important to understand that it’s not about skills per se. It’s about the proper attitude, skills, and experience.

There are three basic levels we can consider here.

Junior

Juniors (entry level people) cannot work independently. They aren’t experienced or skilled enough to deliver results on their own. No matter if we’re talking about software engineers, architects, politicians, or product owners. Juniors can’t work on their own and need some guidance to make progress.

Some claim that “X is not an entry-level position” where X can be whatever (scrum master, product owner, project manager, you pick one). I believe in general this is not true. No matter what our role is, we’ll need some guidance initially.

Mid

Mids (regulars) can work independently but they don’t have this something that distinguishes them from other regulars. They “just do the job”. They aren’t role models yet but they can work on their own. We can rely on them, we don’t need to supervise them, and we can expect they have good enough skills to do the job.

Senior

Seniors understand what their role is and how it interacts with other roles around. They can improve the role and not only themselves. They can grow others, can share knowledge, can teach, and they can adapt to changing conditions.

Notice that it doesn’t mean seniors have great skills. Pretty often they are not well skilled at all. However, they can use their skills efficiently and they understand how to grow up themselves and others around. They use their full potential and unblock the full potential of others around.

Senior Engineer vs Architect (vs others)

Based on previous sections, does it mean that seniors don’t need to be good at coding? Or that being an architect requires teaching others?

The answer is a little bit more complex. There are two basic things that we can recognize in software engineering: roles and skills.

Role

Software engineer is a role. Same is architect (and actually every flavor of architect like business architect, enterprise architect, solutions architect, etc.). Each role requires specific skillset and different roles may require similar skills.

Being that said, we should understand that “architect” is not “the next level after senior”. Software architect is a completely separate role. It requires different skills and generates different results. Therefore, one doesn’t need to become a senior engineer before becoming a software architect. Same with scrum masters, product owners, project managers, or people managers.

This also means that architects don’t need to be “experts” in programming. Just like there may be a junior software engineer, there may be a junior architect. It’s just another role.

Skills

Each role requires skills. Some skills may be needed for many roles, for instance dealing with numbers is required for accountants and salespeople. We can measure how good skills we have and we can typically say that we are novice, professionals, or experts. Obviously, we can recognize more levels or even assign numbers. That doesn’t change the point, it only changes the naming.

When it comes to software engineering, we can later recognize many skill dimensions.

Technology

Technology can be your favorite tech stack like C#, Spring, or LAMP. This can be a programming language, database, framework, library, paradigm, application, or whatever else.

For instance, an expert in C# will be able to explain how GC works, what is generational hypothesis, how locks are implemented, or why we can’t inherit from structures.

Area

Area is a particular part of the application. This can be backend, frontend, infrastructure, database, desktop, mobile, or whatever else.

For instance, an expert in web backend would be able to explain SNI, CORS, SOP, certificates, scaling, impersonation, caching, and other things typically used in web backends.

Domain

Domain is a particular part of the reality that we model in software. For instance, this could be payments, medical applications, high frequency trading, or logistics.

For instance, an expert in banking would explain what a credit score is, how to calculate it, what PCI is, how to deal with international transactions, what is ELIXIR, SEPA, or SORBNET.

Who am I

Based on what we said above, it’s possible that one is a senior software engineer + expert in backends + junior in C# + professional in banking. Therefore, we can see why there are software architects that seemingly don’t know how to code.

Programmer vs Software Engineer

Is there a difference between programming and software engineering? That’s a matter of naming and doesn’t matter much. If one doesn’t like to be called programmer, then let’s call them software engineers. This doesn’t matter at all.

What matters is the understanding that programming is not the only thing in “programming” (or “software engineering”). Much more important is “integrating software over time”. It’s easy to write some code. It’s hard to maintain the code over years when technologies around change, operating systems become deprecated, and network technologies disappear. Actually, writing the code is the easiest part of programming. Maintaining the code over years and keeping it alive for decades is what makes the software engineering (or programming) hard.

In short, it doesn’t matter whether we call ourselves programmers or software engineers. What matters is if we can write the code or can develop the same codebase for decades.

How do I know what to do next?

And now comes the tricky part. When someone says they look for “senior programmers”, how do I know what they mean?

The short answer is: you don’t. You won’t know until you ask them what they mean. And this is how you should look for a job: just clarify what the expectations are, what you can do, how good you are in it, and what you get in return. It doesn’t matter if they call you junior, senior, architect, principal, staff, fellow, or distinguished. That doesn’t matter at all (putting psychological aspects aside). What matters is what your role is and what you’re expected to do. That’s it.

]]>
https://blog.adamfurmanek.pl/2023/02/24/types-and-programming-languages-part-19/feed/ 0
Availability Anywhere Part 20 — Nested full-tunnel VPN in another full-tunnel VPN with force tunnel mode https://blog.adamfurmanek.pl/2023/02/17/availability-anywhere-part-20/ https://blog.adamfurmanek.pl/2023/02/17/availability-anywhere-part-20/#respond Fri, 17 Feb 2023 09:00:25 +0000 https://blog.adamfurmanek.pl/?p=4895 Continue reading Availability Anywhere Part 20 — Nested full-tunnel VPN in another full-tunnel VPN with force tunnel mode]]>

This is the twentieth part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows

We already know how to use full-tunnel VPN from a virtual machine on the host with TCP over File System. Today we’re going to see how to nest full-tunnel VPN inside another full-tunnel VPN.

Imagine that you take your corporate laptop and go to the office. You want to connect to your client’s network, so you use full-tunnel VPN (something like Cisco AnyConnect or Wireguard). Your client configured their firewall to allow your office’s IP address. All works.

Now, you want to work from home. You take your corporate laptop and come back home. You try connecting to your client’s network, but they didn’t allow your home’s IP address to connect. You try connecting to your office’s network with a VPN and this works well. However, you can’t connect to your client’s VPN now because you can’t have two full-tunnel VPNs enabled. How to deal with that?

I managed to get something like that to work with the following setup:

  1. On the host, install Hyper-V
  2. On the host, install Hyper-V guest called VM_A. I used Windows 10 x86. It’s better to use x86 VM to avoid some issues with nested virtualization.
  3. On the host, enable virtualization extension for VM_A with Get-VM | where Name -eq "VM_A" | Set-VMProcessor -ExposeVirtualizationExtensions $true
  4. On the host, connect to Hyper-V guest VM_A with shared drive C:
  5. In Hyper-V guest VM_A, install Virtual Box 4.2.36 which works on Windows x86. Some other version of Virtual Box possibly can work.
  6. In Hyper-V guest VM_A, create Virtual Box guest VM_B. I used Windows 7 x86. Again, use x86 VM version.
  7. Configure VirtualBox guest VM_B networking as NAT with default interface type (something like Intel PRO/1000 MT Desktop)
  8. In VirtualBox guest VM_B, install Virtual Box guest addins
  9. In VirtualBox, configure shared directory \\tsclient\C with full read-write options (uncheck read only) and with automount
  10. In Hyper-V guest VM_A, configure VPN to your office’s network. Let’s call this connection VPN_A.
  11. In VirtualBox guest VM_B, configure VPN to your client’s network. Let’s call this connection VPN_B
  12. You may need to set DNS in VirtualBox guest VM_B to something like 8.8.8.8
  13. Enable VPN_A, then enable VPN_B.

All should work. Mind that in order to use VPN based on layer 3 GRE protocol (like PPTP) for VPN_A, you may need to reconfigure Hyper-V guest to use bridging. I don’t know if it’s possible to use GRE-based VPN for VPN_B in this setup (I doubt that). However, typical corporate VPN-s use HTTPS on the way to avoid issues.

I tested this setup with Wireguard as VPN_A and Windows’ SSTP as VPN_B. It worked correctly. When VPN_A was enabled and VPN_B was disabled, then VirtualBox guest VM_B was visible to the internet from the IP address of VPN_A (so the IP address of the office). When both VPN_A and VPN_B were enabled, VirtualBox guest VM_B was visible to the internet from the IP address of VPN_B (client’s IP address). Also, shared drive from host was visible in VirtualBox guest VM_B when both VPN_A and VPN_B were enabled. Therefore, you can use TCP over File System properly to route the traffic from the host through any of the VPNs.

]]>
https://blog.adamfurmanek.pl/2023/02/17/availability-anywhere-part-20/feed/ 0