Sunday, February 4, 2024

Month 2 - Week 4 - Working on Event Reporting in Linux

 This week has been rather extensive. There was a lot of work involved with not just this class, but my other class as well. I wanted to pour in more time to get this specific bug we'll be talking about fixed and working correctly, however, I was not able to devote as much time as I would've liked. This is primarily because I had to spend a lot of time in the other class working on final assignments. So the majority of my time was spent ensuring that those assignments were handled and that they were turned in on time. As a result, the progress on this bug which I will be discussing in particular is rather slow, but I was able to make enough progress to be able to sufficiently know how to fix it next week.

To give a brief description of the bug, I want to put the key focus on the type of bug that we're dealing with. So whenever an average developer creates a window using our library, they tend to want to keep track of certain events that occur with that window. If you move a window, resize it, or maximize, or minimize it, you preferably want an event to be reported so that way the developer can create certain functionalities that can occur upon any of these events happening. So something I wanted to focus on was trying to determine why this bug occurred. A brief description of the bug is rather simple, if the user maximizes the window and then minimizes the maximized window, no minimize event is reported. It sounds simple on the surface but is actually much harder once you look into the nitty-gritty of it all.

One of the main issues in particular revolves around how these events are determined. I would also like to note that this bug is only occurring on Linux under the X11 library. Whenever an event occurs, the underlying architecture will report over to GWindow to handle it. This carries a couple properties, the main one in particular being the actual property return value. There are a lot of values this variable can be, but there are a few in particular that point to whatever is happening to the window. For instance, if the property value were equal to 384, then we can determine whatever happened to the window resulted in the window's property going to the max vertical height, this would normally mean a resize, maximize, or minimize event.

The first step that needed to be taken was trying to determine why the event wasn't being detected. There were a couple theories that I had, mostly revolving around the conditional logic of how the minimization event was being detected. I eventually concluded that none of the actual conditional logic was the problem. From my findings, it was a preliminary check that was acting up. So the whole reason the minimization event wasn't happening was due to that. The specific preliminary check that I'm referencing is a check that tries to determine if the window has changed all. It checks if there's anything different about the window's property, and by looking at it, if there's no difference, it will break out and report nothing. This is rather problematic though, because a minimization event doesn't inherently change the window. Whoever initially wrote the condition for this if statement also thought the same thing, as there is an additional condition applied to it, ensuring that the window isn't being minimized. However, this is where we face the actual bug.

Whenever a window is maximized and then minimized, the property return value does not report a minimized event. It reports a burst of 4-5 events, all being 384, or 384 with the last being 491 (I may be wrong as it's been a few days since I viewed the exact number). For simplicity, 384 is a property vertical max event, and 491 is unknown. None of these events are reporting the proper property hidden event code. This causes a problem. Since the property return value is no longer returning property hidden, and the window doesn't change due to it only being minimized, this if statement above executes and exits the function without reporting a single event.

There are a couple of things we could do to try and check for a minimize event during a maximize. One of the main checks is seeing if the previous event that was reported is a maximize event. If the previous event was a maximize event, and the property return value is 384, then we could report that a minimize event has occurred. We could also flip this condition to check if the previous event was a minimize event, so that way if you bring the window back up after minimizing, it reports a maximize. This is where the main problem occurs though, because I've already done this code, the window, due to the fact it reports these events in a burst of 4-5 events, we face the issue of it reporting multiple minimize and maximize events. So that leads us to figure out how to lessen that.

To clarify, in its current state, we do have minimize and maximize events reporting properly. The issue we currently have is that it's reporting them multiple times. We only want these events to report once, if it's reporting these events repeatedly, it can cause issues for whatever functionality might be blinded to these events. Since this library is used mostly for game development, you don't want to burst of minimize and maximize events being reported, there could be functionality tied to that game that may break behavior if it's being spammed with events like this. So this is why we need to figure out how to lessen it to just one event being reported.

One of the possible fixes is to use a bitmask or an array that tracks the burst of events, specifically, the property return value. Let's frame it like this. If the window is minimized while it's also maximized, we'll receive the burst of events, around 4-5. If we capture these numbers, and then check if they're equal to 384 or 491, we can tick a bit in the bitmask. We can then check if the bitmask equals 0x11110 or 0x11111. The reason for the two checks is if we only get four events and not five, we will have the last bit be 0, otherwise, they'll all be 1.


This is merely pseudo code, but it does show the idea of how this could work. By doing these checks, and then using the bitmask as a way to track how many times these events are reported, we can use the bitmask as a condition for the minimize and maximize. It would also force the event to be reported only once. This bitmask wouldn't be used anywhere else in the code and would be tracked only for this specific event. However, a change like this will require discussion with Lari and will have to be approved as this introduces more variables, and could have possible side-effects that I may be overlooking. Currently, this is where I am for this bug, but I feel I'm getting closer to fixing it. I feel if I'm allowed another week to tinker with it, I'll be able to get a solution made.

Edit (2/5/2024): I'm going to add a small correction to this blog post as I felt it was needed to correctly explain the bitmask. I made a mistake in my explanation of it and failed to represent the data accurately, this is due to most of my experience with bit manipulation revolving around color. To better explain the actual values, they would need to be displayed in proper binary. So instead of 0x11110 and 0x11111, it would be `0001 1110` or `0001 1111`. The pseudo-code would also be different compared to what was originally shown.


This should hopefully show more accurately what the code is checking for. Here we are checking if the bitmask is equal to `0x0001E` or `0x0001F`, which are just 30 and 31, or `0001 1110` and `0001 1111` respectfully. So this code should actually be more accurate to what the final code would look like.

Sunday, January 28, 2024

Month 2 - Week 3 - The GController Bug is Fixed

 Last week I talked about a bug I was facing involving GController. To summarize, if you have a controller already plugged in before the GController is initialized, GController fails to properly "see" the controller. This was a rather tedious issue at first, primarily due to the fact I didn't realize what kind of bug this was. It was only later during this week that I figured out the bug was around threading and event callbacks. This required two changes primarily. The first was the unit test needed alteration so that it could properly locate and trigger the bug consistently, and the second was the controller setup needed to happen manually before the event callback was called.


The unit test was rather easy, as all it needed was a simple addition to the code. This addition is to make the main thread sleep. If the theory is that the event callback works on a separate thread, then we can make our thread rest while the event callback thread does what it needs. So, if the unit test is being done correctly, it should start and initialize the GController, get the connected controller count through GController, wait for one second, and then do a manual count of controllers connected. What this should do is find an inaccuracy. If the user has a single controller connected, the count when GController is initially made should be one. If we wait for one second and then check again, the count from the manual check should also be one. However, with the bug present, what happens is the initial count is zero, and then after one second of waiting, the count in the manual check is one. Since both counts don't match, there is a discrepancy, resulting in the test failing. This means the test located the bug happening, and that there is a bug present.


The second change was easier, as all I needed to do, was take the event callback lambda that was being used and store it inside of an auto variable. This variable will effectively become a function built solely for this initialized function inside of GController. We can then pass this variable to the daemon to make the event callback, and then call the lambda function itself after. This should make it so that when GController is initialized, it will immediately have the information it needs to properly work with the controllers as intended.

From this, I waited for the next meeting and then started discussions with Lari about this bug fix. We eventually concluded that the bug was fixed through his testing, and we discovered something else that was also interesting. Lari mentioned to me that there was another bug with manual unit tests for GController. What would happen is that GController would initialize in one of the manual tests, and if the test were to fail and move to the next manual test, the next one would fail outright, refusing to see any input mapping on the controller, resulting in missed inputs. When he went to show me this bug in action, however, he couldn't recreate the issue. I started diagnosing the problem and reading through the code but came to the conclusion that the bug I had just fixed was also responsible for the bug he was experiencing with the manual tests.

My theory is that the previous test wasn't breaking the next test, but more so, the test that was failing was due to the event callback not happening in time, thus the controller is not seen nor initialized. This would result in the input mapping not being configured correctly, and thus, when the test is reading or looking for input, the controller isn't registered, making the input "miss" even if you press the right button. So, indirectly, I ended up fixing two bugs at the same time without knowing it. I'm happy I did, however, because the more stable the code is for consumers and developers, the better.

Now, this would normally be the end of the post, however, I need to also talk about my next bug. There is a bug right now that involves the infamously hard-to-work-with X11 framework in Linux.


This bug is specifically around event reporting, or lack thereof with X11. The way this bug happens is simple. Take a GWindow, maximize it, and then minimize it. If you do this, you will have no minimize event reported due to the window being maximized. This is mainly due to conditional issues in the code. I've already located the problematic source in particular and will be working on it soon. The only reason I haven't started yet is due to the setup and steps I'm taking to properly get everything working correctly.

I decided that to tackle this bug, I would install Arch Linux on my system, and get it configured for development, so that way, I can fix the bug on my main workstation. The conditional check in specific is already located and marked as a comment on the merge request.


So since this bug has already been located and marked, I just have to prepare my workstation to resolve this bug, which I'm thankfully almost done with.

So far, I'm using an Arch Linux manual installation with Fish as my shell, I have KDE Plasma running under Xorg as my main desktop GUI experience. I'm also prepping some specific IDE-related things to get all of the code work ready. I should be finished with all of the configuring tomorrow, I just have some things with Vulkan that I need to take care of, and then I will be good to go. This bug will be my primary focus next week.

Sunday, January 21, 2024

Month 2 - Week 2 - GController Bug Investigation

 Last week I spent time correcting and fixing a bug relating to macro usage and the context of what the macros actually represented. I'm happy to say this bug has been fixed and is now merged. Stepping further into GController, however, there are other bugs present that I decided to investigate. Particularly, there is one I noticed that piqued my interest.


I read through this issue and immediately wanted to diagnose it. At hand, the issue is rather simple. If you have a controller already plugged in before the GController is initialized, GController fails to properly "see" it, resulting in inaccurately reported information. For instance, if you were to call the function `GetNumConnected` which returns the total connected controllers, it would return zero instead of one if you had a single controller plugged in before GController is initialized. I decided to make a little unit test that would check for this inaccuracy, and then determine how GController would react. GController initially reported the unit test passing, which didn't sound right. I updated the unit test a little more, this time to provide printouts for the reported number of controllers compared against a manually checked reported number. To my surprise, they both were reporting zero, meaning that GController didn't even register that the controller was there at all.


This misreported number is unsettling, especially because the manual check is supposed to catch the inaccuracy and fail the test. After around two hours of researching, I reran the test, and amazingly, it failed. It worked as expected. The controllers connected reported zero, however, the confirmed controllers connected reported one. So, off the unit test, we have a bug that may or may not show at all. I decided to dig into the code at this point and debug.

From the debugging, I came across code internally that seems to be the problem:


In the initialize function, we see an event callback that is set up for XInput. In this event callback, we can start to piece together how the function is working. We call onto XInput to get the state of each controller slot, if the controller is connected, it will then work on setting up the controller, mapping what's needed, and then revealing it's connected. Something of note is that this code is bug-free. It works as expected, and also detects my controller fine. The problem is it is an event callback. The reason this becomes an issue is simple.

What if, by chance, you initialize GController and test for a controller before the event callback is called? What happens is the bug we're experiencing. This callback is being handled on a separate thread (if my information is correct). So what happens is the code we call and execute is happening too fast, and due to that, XInput hasn't initialized the controllers yet, which results in GController showing that no controller is plugged in. This means one of two things. We either need to initialize the controllers manually before XInput does, or we delay the program long enough for XInput to initialize itself. There is possibly another way, which is initializing XInput, and then calling the callback itself after (this assumes the back-end is created for XInput to allow this), but currently, these are the options left to choose from.

There is no resolution for this issue currently as I'm still working on getting it completed and fixed. From what's inspected though, I have a pretty good idea of what's happening now. So all that is needed is to test more, make changes, and see if this can be corrected.

Sunday, January 14, 2024

Month 2 - Week 1 - Fixing GController's Incorrect Macro Use

 This week consisted of a lot of work from the previous month. Some of it included more MSAA testing, finalizing changes, and last touches, but there were some new things. One in particular was a bug I had assigned myself to last month and haven't had the time to fix, at least until now. With MSAA implemented, I took a look at it again and decided I would work on getting it fully fixed this week. This bug is directly related to the GController, specifically a macro called `G_MAX_CONTROLLER_INDEX`. The problem with this macro was that it was being incorrectly used. In some cases, it was treated as the max count of controllers, but in others, it was treated as the max index for the controllers array. The name of the macro itself can cause confusion upon seeing how it's used in code, and I figured it would be a perfect bug to work on right after finishing my first major feature.


We can see in the issue that it had been sitting for over a year. Admittedly, this issue is rather small, and not a major problem. However, it has been a source of confusion. Due to the name it carries, users of the library could end up mistaking it as the max index for the array, when it's more a max count of controllers allowed. To approach the issue, I waited until the Wednesday meeting where I could talk with Lari, my mentor, and Colby, the discoverer and creator of the issue above. We discussed how the changes would have to be handled, what ways it could be fixed, and the possible problems that fall into editing GController itself.

The route we chose for this fix was a rename. Simple enough. However, we needed to correct parts of the code that were using the macro as a max index. The rename would also modify three macros: `G_MAX_CONTROLLER_INDEX`, `G_MAX_XBOX_CONTROLLER_INDEX`, and `G_MAX_XBOX_CONTROLLER_INDEX_XBOXONE`. For each of these macros, we will rename them so that `INDEX` is replaced with `COUNT`. We have to update these changes throughout the source code and ensure that they are being used properly. There are some problems with the changes being made, however.

Firstly, testing any changes to the code is going to be a process. Since I only have one controller, an Xbox One controller, I can't test the functionality of what's being edited. This testing has to be done by Lari, as he has four controllers that can be used. Secondly, we have to modify some logic in the code, primarily the array bound checking that is being done for `GetState` and `IsConnected`. These checks are currently using the old macros, and they're also using them incorrectly, checking one over the array length (due to zero-based indexing). Lastly, we have to ensure that it runs cross-platform. We can't do this through traditional means as we don't have the proper tooling for it. Our only workaround for this is the runners for GitLab, but they can't test physical controllers, so only the basics. This means every edit we do has to be thoroughly thought out and looked over before they are pushed. This is to ensure we don't indirectly introduce a bug to the codebase, only to repeat the process all over again.

So with the possible issues known, we approached multiple ways to fix them. One has already been mentioned, that being Lari testing the primary manual tests with controllers for Windows. This will cross out Windows and UWP from the list of platforms needing testing. Mac and Linux will have to be handled differently. For those platforms, we will focus more on ensuring code changes are light and efficient. The changes done for them have to be reviewed and have to be ensured to have no logical errors in them. It's not the best fix, but it's the best we have currently. Then, naturally, there are the runners, that will make sure the code compiles okay, and that nothing will slip through for a majority of other tests.

Another fix is the logic changes. I decided that after renaming the macros, I would introduce three new macros that have the old name, however, these macros will equal the max count by subtracting one for zero-based indexing. This means that max count exists for physical controller count, and max index is for array indexing. They are separated. Because of this, I can replace instances of index checking in the Win32, UWP, Mac, and Linux code with the new index macro. This will keep readability while also showing how both macros are intended to be used. 


After these changes are done, we can reflect them in the actual code itself.


Then we also update the needed logic checks to better fit the new index macros.


You can probably tell that since the index macros contain the same name as the old count macros, we don't have to replace the macro name, more the logic itself so it is more in line with expected functionality. If the controller index is equal to the max index macro now, it is on the last controller in the array, compared to before where it needs to be greater than or equal to the macro. There was also worry with the old macros since the index checks could be checking one over the array's size, possibly causing overflowing in the library code itself, which is no good. The replacements and logic changes here feel more grounded, and ensure that everything is being used for proper purpose.

With all of the changes done, the main thing to do now is await feedback, have manual testing done, and ensure that all of the code is correct by code reviewing. Once these are handled, then the bug fix can be merged, and I can move on to the next bug on the list. The general workflow for this was very easy, and I didn't find it all too hard to get into. After implementing my first feature, I understand much more about how this library functions, what each class does, and how the code is compiled and formed. After getting a grasp of everything needed, I find the bug fixing to be more of a cooldown, especially in comparison to the nearly 2,600 lines of code I added to tests alone for the MSAA implementation.

Sunday, December 17, 2023

Month 1 - Week 4 - Adding MSAA to DX11

 So, last week was spent primarily focusing on getting the setup process for Windows more fine-tuned for users to contribute towards the project. This was a rather important task, and a lot of time was spent getting everything done correctly, but last week was also the week that it was finally completed and merged. With that, this week was spent working on my first real feature. This feature is MSAA support for DX11. To implement this, it needs the whole package, unit testing, research, and actual implementation code to get working. A lot of time was poured into this, and a lot of research into MSAA in general. While I could explain the full workload that was taken for this, I will instead explain two problems I faced with the implementation, and what I did to figure it out and get it working properly.

One of the first steps was adding the values for MSAA to the allowed mask variable, but I also needed the sample count for whatever was passed. This process was fairly straightforward and provided little issue in the actual implementation.


The second step was going through and altering the swap chain description and all texture descriptions to reflect the multisampling. This was also fairly straightforward as shown above. However, it was the code after this that proved to be more problematic.


Now, this image shows the solution, but to get the fix for this was a little troublesome. To explain what this is doing, I need to break down what I think is happening and what resulted in the code above. When I was initially implementing MSAA, I was running into a problem where the stencil view was failing to create, this was a constant error and had no obvious source other than the stencil view being null. Through the internet and discussing the problem with my mentor, I came to realize that the stencil view may not have a large enough buffer to hold all the pixels needed for the multisampling to work correctly. I looked around online and found a simple fix. By changing the view dimension from D3D11_DSV_DIMENSION_TEXTURE2D to D3D11_DSV_DIMENSION_TEXTURE2DMS, we can create a buffer that can store the appropriate amount of pixels. All we need to do is check if the sample is greater than one, one being no MSAA, if it is, then we set the view dimension of the stencil view to the proper setting so the multisampling works. After everything is implemented here, we now have MSAA.

This, of course, looks over the immense amount of work that was invested into the tests. There were almost 3K+ lines of code added just for the tests to ensure that MSAA was working properly, and there were many issues that came along the way with it.


A good example of the test code needing problems fixed is the one above. There was a problem I encountered with the tests I was doing when trying to do a test for MSAA x16. Although x16 MSAA has been around for a long time, modern GPUs will sometimes still lack the support for this level of MSAA, so what happens in the test is it will try creating an x16 surface for DX11, and immediately fail. A failed unit test is something we don't want, especially when it's a hardware limitation. So the way around this is to check for it. Normally, the REQUIRE statement would contain the create function call, and then we'd check the return immediately to see if it is a success, however, this has to be changed. What has to be done now is call to the surface, attempt to create it, and then check if the result is first a HARDWARE_UNAVAILABLE error. If this error is returned, then we can simply skip it because this isn't the fault of the test itself, but rather the hardware. Now, if the hardware is supported, this return from create will not occur, and we can REQUIRE the result is a success and nothing else. This problem took me a long time to handle. I had to research, redesign, and approach how to fix it. On a quick glance, the fix was probably easy to spot, but for me, just learning how everything works, I spent much longer on it than I would like to admit.

With that, however, everything else flowed smoothly and the implementation was rather easy, minus all of the writing time required for it. It was interesting learning all the internals of the library to get this working, and ensuring both the desktop version and the UWP version worked. All there is to do now is wait and see how the code review goes, and if any corrections are needed to be made.

Sunday, December 10, 2023

Month 1 - Week 3 - Finishing Touches with Setup Process

 Last week I touched on the setup process and worked towards taking steps to make it better. To do this I made a bat script that ran a PowerShell script which installed Chocolatey, and then installed the NuGet package to work with CMake. This was done to avoid the issue of NuGet not being installed when the UWP CMake needed it for proper project generation. There were two catches with this approach. 1) To install Chocolatey, the user needs to be an administrator, and 2) the user has to be an administrator to even install the package as Chocolatey will force a prompt on the user if not. This prompt can be bypassed but locked on a thirty-second timer, even with arguments to attempt auto-accepting it.

Due to this issue in particular, I spent the early part of this week working on finding a solution to the problem. The reason for this being a problem is due to the runner for UWP and Windows compilation. Both runners for these builds can't perform input to bypass these prompts, so the prompt is either cut off from performing the build completely or has to wait thirty seconds for the prompt to auto-accept.

I don't have pictures for the iterations on the work put into this, but some of the iterations involved trying to bypass the prompts through PowerShell directly, and some involved completely remaking the scripts in an attempt to fix it, but eventually, I settled on a solution.

Chocolatey had to be replaced.

At first, this sounds like a large process. My requirements for a package manager are 1) it needs to allow no input, 2) it must allow running without an administrator, 3) must be able to install packages with no prompts, and 4) must be updated regularly so the version of the package isn't old. This search for the right package manager could've been long and difficult, but there is a package manager right now that is perfect for the task. It was a package manager I had been researching just a couple of days before encountering the issues I did with Chocolatey. This solution is WinGet.

As the image above explains, WinGet is a command line tool that is tightly integrated into Windows 10 and 11. This tool allows users to discover, install, upgrade, remove, and configure applications. This is the client that interfaces with the Windows Package Manager service. The big perk with this is it is not only already installed by Windows, but it doesn't prompt for administrator access unless the application requires it, which is much more preferable since NuGet doesn't need this to install. With this, I went to work on the code immediately.

The first step is updating the bat file. Originally, the file would open PowerShell, and then open another PowerShell within with the argument to open with a UAC prompt. This has been altered to now just open the PowerShell and execute the script without any extra work. This has the benefit of making the script look much more concise now.

The next part is to update the PowerShell script. The first thing needed is to test the `winget` command to see if it works. We can print the version number, and then check the exit code, this exit code will show whether or not it is. If it isn't, the user has to install the App Installer program from the Microsoft Store, which is side-loaded with `winget`. After this check is done, and if it passes, it will then use WinGet to install NuGet, using a couple arguments with it. The first argument `-e` is checking for packages with the exact name as requested, the second and third will auto-accept any package or source agreements, with the final forcing a direct run of the command and continuing with non-security related issues. This will ensure no prompts are given, and that the package will install smoothly for the user automatically. However, there is a final step.

WinGet, upon installation of the package, will require the shell to be completely restarted for the path environment variable to update. This is required for the NuGet process to be used and seen by the shell. But, the runner can't do this, and the user would need to run two scripts to make this efficient. To get around this, we can do some PowerShell magic. The image above shows us setting the environment variable `Path`, which is temporary for the instance to an updated path. We do this by accessing the system environment and getting the environment variable of the same name. This allows us, without restarting, to use NuGet freely throughout the setup.

With that, the runner is now able to do the setup script without any issues. This pull request was merged, and will hopefully make setting up on Windows easier, especially for UWP where this specific NuGet requirement was needed.

Next week, I will be focusing heavily on getting work done with another issue involving DirectX11 MSAA support and will be making posts for that as well.

Sunday, December 3, 2023

Month 1 - Week 2 - Project Setup Updated

I've started working on updating the setup process for Windows users developing on Gateware. Due to recent UWP issues that we were experiencing the previous week, we found that NuGet was required for the project and wasn't being handled appropriately when it wasn't found. I was assigned to start working on updating the script used to set the project up for use. The previous version of the script was a single bat file that would create needed directories and then call CMake to generate the projects.


The code for it was simple, but what was needed for the change was to convert this bat file into a PowerShell script. Specifically, the new process needed to get Chocolatey, install NuGet with it, and then perform the same operations as the bat file.

There were a couple issues with this, however. The first was the execution of the script. The script is stored in a ps1 file, this file is the default for PowerShell scripts, and to run this script can be tricky. Traditionally, PowerShell wants the script to be signed to run without further scripting, however, the signing process is confusing and a little strenuous to my knowledge, so I looked at the other route. This other route is setting the execution policy of the PowerShell instance to bypass. Doing this allows for the script to be run without needing the script to be signed. this was the route I ended up taking for this. The second issue that I ran into was for Chocolatey installation. Chocolatey requires that PowerShell is run as administrator to install itself correctly. This doesn't sound like an issue at first, but let's take a step back and visualize how a user would set up the project on the old and theoretical new.

If I wanted to generate the project back then, I could just start the bat file, wait for CMake to generate the projects, and then I would be ready to work. That's if I have NuGet installed and configured in the path beforehand. Overall though, NuGet configuring would only need to be done once, and then the process from there is quick for each following setup on that system.

Now on the new system, I would have to open PowerShell as administrator, and then call the command `Set-ExecutionPolicy Bypass -Scope Process -Force` before finally calling our script. This isn't just something that has to be done once either. This process would have to be done every time the user needs to set up the project. Of course, we want this done easily, so this is where I started problem-solving.


Firstly, apologies for the text being hard to read, since commands in bat can't be broken into multiple lines, it had to be written rather long.

Now, this image is of the bat file replacement I wrote. What this bat file does is execute an instance of PowerShell that will execute another instance of PowerShell, except this second instance will run as administrator and be fed two commands. One command is to set the location to the active directory used by the bat file, and the second is to execute our script. Pretty straightforward, but there were problems with this.

You may have already noted that two PowerShell instances to run one as administrator has a bit of code smell, and you would be right. However, when I attempted to use the much more straightforward `runas` command that the bat script already has, it seemed to have trouble executing the PowerShell process with any arguments fed to it. This is problematic as I want the bat to handle everything for the user besides the UAC prompt to run as administrator. The only workaround I know of is this solution I made above. It's not the best by any means, but it gets the script executed.


Now, moving to the PowerShell script itself, we had to do a couple things immediately. The first is ensuring the script is being run as administrator. The code for this is a little lengthy, but surprisingly readable all things considered. After this check, we then check an absolute path to see if Chocolatey is installed. The reason I'm using an absolute path here is that Chocolatey will install here always. I don't think Chocolatey lets you modify where it installs, however, if it does, we can update this down the road to support other paths. For now, it's using the default location. If the directory where Chocolatey installs doesn't exist, we call on the installer and install Chocolatey for the user. After this installation, we immediately install the required package, NuGet.


From here, the rest of the code is simply a translation of the old bat script to PowerShell. A lot of the code stayed the same, except for the use of `errorlevel` which was switched out with `$LASTEXITCODE` since the previous was specific to bat scripting and doesn't exist in PowerShell. 

Overall, the process of implementing this new system took around 2-3 hours and was relatively error-free besides the issues I mentioned. I had to take a little bit of time to design and ensure the developer running the bat file would have to interact as little as possible so that the process could be mostly automatic. I feel that the result is good and works well, but there is still the code review that will be needed for this to pass, as well as testing on another system to see if it works on other systems that are not my own. Once these pass through, the new Windows setup script should be merged and a part of the project.