Wednesday, August 31, 2016

Windows 10 upgrade broke one of my Hyper-V machines

I did an in-place upgrade to Windows 10 from Windows 8.1 Pro a while back. Shortly after that, I went to the Hyper-V Manager to start a couple of the VM's I've put together over time. One of them refused to start, citing a failure to load an AVHDX file. Some Google results suggested deleting the saved state, but that VM had no saved state. Upgrading the configuration version did nothing. All useful changes to the VM's settings failed because of the inability to load the virtual hard disk. I tried deleting the checkpoints (which seemed to be the locus of the problem), but got the same error.

None of the Things On The Internet that I could find worked. Since it wasn't a super important VM, I just deleted it and reinstalled it fresh from the ISO I still had.

Tuesday, August 30, 2016

ADMX support definitions can reference other support definitions

I just did some science on the "Limit the maximum network bandwidth for BITS background transfers" policy setting to see if the real Group Policy Editor actually resolves references to support definitions from other support definitions. I filtered the policies for ones that support Windows XP SP2 (which is only attached to that policy setting via a referenced support definition), and that one showed up. Therefore, the Group Policy Editor can handle cases like these.

I'll have to change Policy Plus's AdmxBundle class to try to fold referenced definitions into containing ones. Alternatively, I could adjust the support definition class to be able to hold a list of mentioned definitions, then the component that actually cares about support would be responsible for avoiding infinite loops or stack overflows.

Monday, August 29, 2016

Removing domain user profiles from a Windows machine

Local accounts can easily be removed in the appropriate section of the Control Panel. How to remove domain profiles, though, is not nearly so obvious. This is how to completely remove an arbitrary user profile:
  1. Open the System control panel section (not the Metro one)
  2. Open the "advanced system settings"
  3. On the Advanced tab, click the Settings button in the User Profiles group
  4. Select the target profile and click Delete
The dialog will take a while to load. I think Windows goes through the contents of each profile to calculate its size, which is an expensive operation. It's a little bizarre, in my opinion, that there are two versions of this dialog, only one of which is useful in any way. Typing "user profiles" into the Start menu's search box will produce one that's missing almost all the profiles.


Searching for "user profiles" brings you the left version, while going through the System control page produces the right one.

Sunday, August 28, 2016

Policy Plus - Details

Today I made a handful of small information-displaying forms, one for each type of policy object. These forms contain internal information from the ADMX files, like display code, unique ID, or source file. The unique ID is particularly useful because, for categories and policies, it can be used in the Find by ID window. The other info is just nice to have, it saves you from having to poke through the ADMX files to find something.





I also added a context menu to the categories tree and the category listing in the main form. It currently contains only Open or Edit (for categories and policies, respectively), plus Details, which produces the appropriate details form.

While testing, I found that the ADMX files occasionally contained support definitions defined in terms of other support definitions. I'm pretty sure the real Group Policy Editor ignores those, so I had to Support Details window display such entries as their ID (as opposed to crashing).

The changes are live on GitHub.

Next, I'll see about adding an inspection window for policies' Registry values.

Saturday, August 27, 2016

Running PowerShell on Ubuntu

A while ago, Microsoft open-sourced PowerShell and made it cross-platform. You can download the appropriate installation package on the Releases page of that GitHub repository. For instance, if you want to install it on Ubuntu 14.04, you would download the DEB file and run these commands:

sudo apt-get install libunwind8 libicu52
sudo dpkg -i powershell_6.0.0-alpha.9-1ubuntu1.14.04.1_amd64.deb

Oh, and in case you're wondering, you can totally run it in Bash on Ubuntu on Windows 10.

It's a little confused about the cursor position, but it runs!

Friday, August 26, 2016

Artificial Intelligence Stack Exchange is up and running

About three weeks ago, the Artifical Intelligence Stack Exchange site proposal launched into private beta. We've had a good amount of activity since then, and though there is still a little disagreement about scope, we're working through it. Four days ago, Stack Exchange gave us the green light to continue into public beta! This phase will last for at least a few months, after which we'll become a full, graduated, visually-styled site on the network.

You can visit the site at ai.stackexchange.com.

Thursday, August 25, 2016

Markeen - Smart profiling with splitters

Yesterday, I saw that a lot of the exposure of platform inner tiles to air was allowed by the original levels. Since the profiler wasn't aware of the level-splitting infoplane bars, it thought those things were really supposed to be together. So today, I wrote a routine that breaks a given level apart into the visible regions. (To test it, I added a new subcommand - "explode" - that splits every level into its regions.) That routine is used before profiling each level, and it has improved output quality at least a little.

I continued trying to improve the erasure routine. It now tries several times to erase a 5x5 centered on each failed tile, but that still doesn't do the trick. I experimented with switching fill orders after the initial pass, but got no noticeable improvement. I tried changing the scoring mathematics again, but just got worse levels no matter which way I adjusted the base (originally 2) of a certain exponentiation.

Since a smart filling pattern is probably an important thing I should do, I refactored the probabilistic single tile placer out of the inner loop into a lambda so it can be called from different places easily. I have an idea here that might dramatically improve output quality.

Finally, I added a seed-setting feature, so the user can optionally provide a /seed=314 switch to specify the RNG's starting value. I tested it and Markeen can now regenerate a level set when given the same seed.

Time for more pictures!







Wednesday, August 24, 2016

Markeen - Finagling

Today I did some more tinkering with Markeen. First, I made rare structures automatically be removed when they intersect with something already built. That effectively removes all generation of rare structures, but at least they won't be weirdly smushed against normal level parts.

After that, I noticed that the erase-and-regen passes were never running; I had basically commented them out. I re-enabled them after changing the eraser to not run when the retry loop is about to give up. So far, I'm not sure it's helped.

Then I started messing around with the mathematics of tile selection. I tried changing the devaluer of tiles with absent precedents to be independent of the profiling depth, but the only other way to write it that I could think of caused all the levels to be huge messes of all kinds of different things strewn about next to each other.

I noticed that a lot of the issues with the generated levels were from errors in the original ones, like platform inner tiles being exposed to air. I extended the command-line part of the utility to be able to edit profiles. Currently, it can only remove an arbitrary tile entirely or make two arbitrary tiles never appear next to each other, but it helped me get rid of some common problematic configurations.

I also experimented with the previous fill orders, but soon remembered why I wrote the current one. The others all produce suboptimal results, though the corner-expanding one is pretty good at making interesting pipe designs in the solid metal chunk it usually creates. Along the way, I fixed a bug in the current order that caused a hang after being restarted.

While trying to sort out the over-packedness from some of those orders, I tried introducing a change to the final tile selector that buffs the chance of air (tile 0). That had the interesting effect of producing swaths of blankness in some areas - usually around the center of the level - while not having too much of an effect anywhere else. It could still be a good idea if executed with a little more sophistication.

So that's all well and good, let's see some pictures!






I then, for fun, tried it on Keen 4.


You can see the "black hole" effect right in the middle. Keen 4 is already pretty spacious (compared to Keen 5 at least), so the air buff isn't really helpful. So I disabled it to take the following images.



These pictures are only the best levels that I saw, so there's definitely a long way to go yet. Perhaps I should have a system that completely excludes a tile from a level if another tile is present anywhere - that would drastically cut down on the bizarre fusions of platform styles.

Tuesday, August 23, 2016

Markeen - Rare structures are rare now

A problem previously present in Markeen-generated levels was that they had way too many "rare" structures. As a review, rare structures are groups of multiple tiles that only exist in that configuration. Whenever one tile in the structure was placed, the rare structure builder automatically put the rest of the group in place, effectively multiplying the probability of those structures by their size.

So today, I put in place a counterbalance for that multiplication. After a rare structure is generated, the dice are rolled and the structure is completely erased if the roll fails. The RNG's upper bound increases linearly with the structure's size. This has successfully cut down on the number of rare structures while keeping surviving ones intact.

Come to think of it, this post-generation erasure step could help remove preempted rare structures. A failure to place a critical tile could trigger the erasing routine, freeing up that space for normal tiles.

Monday, August 22, 2016

Policy Plus - Open all Registry roots

It came to mind that Policy Plus had no way to edit the Registry of users who are signed in - the "user hive" option won't work when the hive file is already mounted. Also, it couldn't open arbitrary hive files for computer policies.

Therefore, today I updated the "Current Registry" policy loader to accept an argument: a rooted Registry path.


Entering a path after the root works too. Therefore, it's now possible to treat any Registry key as a root. Computer hives can be edited by manually mounting them and typing the mount path into the appropriate textbox. Editing computer settings in that way is dramatically less likely than editing a user hive, so at the moment, I don't think it's worth complicating the UI for.

Sunday, August 21, 2016

Policy Plus - Find by Registry

Yesterday, I implemented a find-by-text feature for Policy Plus policies. Today I adjusted its searcher to be a lot smarter. It now understands quoted strings and wildcard words. There's also a new Find Next feature: striking the F3 key or clicking FindFind Next now causes the focus to be moved to the next policy in the search results that's visible. The Search Results menu item's hotkey was changed to Shift+F3.

After committing those changes, I added a find-by-Registry feature. The query dialog has one textbox for a key path/name and one textbox for a value name. Only one of them has to be filled, but if both are, policies must have a Registry key-value pair that matches both.

Searching for policies that affect a Registry key with "peernet" as one of the path components
Results of the above
The results can be narrowed down by also searching for a specific value name. For example, adding "disabled" in the value textbox results in only four policies being found. Specifying that "peernet" is the last path component (by putting *\peernet in the key field) narrows it down to one.

The changes are live on GitHub.

Policy Plus - Text search

Today I put together a text search feature for Policy Plus. A Find by Text window lets the user enter the search terms and which parts to search for the text (title, description, and/or comments).


In the above picture, the user is searching only for policy settings that have "control" and "panel" in the title. Clicking Search starts the find operation in the Search Results window.


Selecting a found policy and clicking Go opens the setting editor and, if possible, navigates to the policy's category in the main form. If the user reopens the results dialog without doing a different search (e.g. by pressing F3), the same form is shown without searching again. It even remembers which item was selected for ease of keyboard arrow use.

The next thing to do is implement "find by Registry", which will use the same results dialog, but a different query window.

Friday, August 19, 2016

Policy Plus - Keyboard friendliness

I realized today that I never explicitly set tab order for any of Policy Plus's forms, so the focus goes all over the place when the user presses Tab. Also, I hadn't put any effort into keyboard navigability, so dialog boxes couldn't be closed with Escape.

So I fixed both of those things. All forms have a reasonable tab order, and it should be possible to do anything imaginable using only the keyboard. Pressing Enter on the policy list, for instance, simulates a double-click on the currently selected item. Even the dynamically created controls in the setting editor dialog have a tab order assigned at creation.

It turns out tree view controls have keyboard shortcuts, which is a great little Windows feature that I just learned today.

Thursday, August 18, 2016

Policy Plus - Comments

One very clear UI element that was previously unimplemented in Policy Plus was the comments section of the setting editor. Today I took advantage of the CmtxFile class to load and save comment files, updated the setting editor to see and modify them, and implemented the Comment column in the main form. It works, and is 100% compatible both ways with comments made by the real Group Policy Editor.


I also added a checkbox to the Open ADMX Folder dialog that controls whether the ADMX workspace will be closed before loading the folder's contents. That should cut down on errors produced by duplicate IDs in the AdmxBundle.

Wednesday, August 17, 2016

Policy Plus - Works on Home

After installing a fresh copy of Windows 7 Home Premium into a new VM, I tested Policy Plus in it. I immediately noticed that Windows 7 doesn't come with a modern version of the .NET Framework. I installed v4.0 in the VM, but Policy Plus crashed at startup with a bizarre error about Marshal.SizeOf. I installed v4.5, but I got a different bizarre error. I actually needed to install v4.5.2, but in the interest of working on as many machines as possible, I changed Policy Plus's target framework to v4.5.

I then found that though Home editions have some ADMX templates (and Policy Plus loads them fine), it's missing a lot of the more interesting ones. Fortunately, ADMX files can be downloaded legitimately from Microsoft for free, and Policy Plus is happy to load them from any folder.

When I tried to save some changes to the local GPO (which doesn't exist by default on Home), the program crashed because it couldn't find gpt.ini. I added a check for that; now that file will be automatically created if it's missing. I also discovered that Home editions do have gpsvc.dll - the file I was testing for to determine Pro status - but not secpol.msc, so that path test has been changed.

When testing the Registry diff-and-apply routine, I found that instead of being converted to the appropriate object type, POL entries were being passed to the Registry functions as byte arrays (whoops).

Then the big test: whether Windows Home actually cares about these Registry entries. It does, at least with the ones I've tried! RefreshPolicyEx doesn't throw on Home, but it doesn't do anything either, so a reboot or logon/logoff cycle is needed for computer or user settings (respectively) to take effect. The goal of working on all Windows editions is accomplished!

Running perfectly on Windows 7 Home Premium
There's still a lot left to do, though. There are still no comments, no way to search by name/text, and the UX could use polishing. I also have a lot more features in mind that haven't been started yet.

The changes are live on GitHub.

Tuesday, August 16, 2016

Policy Plus - Saving!

Today I did a lot of coding.

I started off by defining a PolicyLoader class, each instance of which is responsible for keeping track of the lifecycle of an IPolicySource. The loader figures out where to load a file from based on the option selected in the Open dialog, then constructs the policy source, then saves the policy source when asked to, then does any teardown that's required before the program ends. It also can tell callers whether it's actually writable, and though I haven't implemented any comment functionality yet, can provide the path to the CMTX file. The Open dialog constructs a loader for each source (user and computer), then the main form invokes its functions when necessary.

All the various sources can be opened successfully. Implementing the ntuser.dat loader was very difficult, as it required a lot of very fiddly P/Invoke. (Memory alignment issues are very difficult to debug!) That loader type is the only one that has to do any cleanup, since it has to unload the Registry hive.

All loader kinds can save successfully too. I had to make a few changes to the setting editor to allow for the policy sources to change during the process's existence. I also made the OK and Apply buttons automatically get disabled when the active policy source cannot be written. RefreshPolicyEx works perfectly as a way to cause policy reprocessing, for the normal local GPO and per-user GPOs. Before invoking the refresh, Policy Plus increments the "version" field in gpt.ini so Windows notices the changes. The save results dialog displays exactly what is done for each section. In most cases, in involves saving to disk, but when editing the Registry directly, the changes are already saved.


In other words, it works! I haven't committed the changes to GitHub yet because I'm waiting for a new Windows 7 Home VM to finish setting up so I can test all this in there. (My previous test environment was broken by the Windows 10 1607 upgrade.)

Monday, August 15, 2016

Policy Plus - Open dialog

Today I again intended to implement a save feature for Policy Plus, but this time I got distracted thinking of all the places it should be able to get policy from. The result was this Open dialog:


This is what all the choices refer to:

  • Local GPO: the GPO you see when you use the real Local Group Policy Editor
  • Local Registry/Current Registry: the real live Registry of the current system or user (no POL file)
  • POL file: edit an arbitrary POL file on disk
  • User GPO: a per-user GPO; the browse button shows a username-to-SID translation tool
  • User hive: an ntuser.dat file; the browse button shows a list of user profiles
  • Scratch space (null): absolutely nothing - appears totally blank and saving it goes nowhere
The User GPO option deserves a little more explanation. The GroupPolicyUsers folder under System32 can contain folders named for the SID of the corresponding user. If such a folder has an appropriate gpt.ini and User\Registry.pol, it will be automatically processed as an extra GPO for the user. As far as I can tell, the normal Group Policy Editor can't view or edit these. They also don't appear to be documented anywhere. Somehow my user account on my machine has one, and has had it for quite a while. Sneaky!


The "User hive" option's browser just enumerates the subfolders of Users (excluding junctions and symbolic links) and checks whether write access can be gained to ntuser.dat. It uses that information to show whether the user hive is currently accessible.

Not running as admin
In other news, I found out from a Blog On The Internet that the "Group Policy Objects" key I was concerned about yesterday is actually just a temporary artifact of the normal Group Policy modification API. In most cases, it only exists while the real Group Policy Editor is open. Therefore, I can completely ignore it.

Tomorrow, I'll hopefully be able to actually open and save all these things.

Sunday, August 14, 2016

Policy Plus - List element handling

I had planned to finally implement the "save policy changes to disk and Registry" feature today, but the Windows 10 version 1607 upgrade took really long, so I didn't get to do all I wanted. I did manage to add an editor for <list> policy elements, which was missing before. Clicking the Edit button on one of those elements produces an editor dialog very reminiscent of the one in the real Group Policy Editor.


For elements that allow the user to provide names, the dialog grows a Name column.

I also found a bug in PolFile's importing of string values. Previously, it could read strings fine, but writing them back would butcher their encoding.

In terms of progress toward saving to disk, I dug in a little deeper on how Group Policy knows a refresh is needed. There's (sometimes) a "Group Policy Objects" subkey in the Registry that contains all the entries from the corresponding POL file, and a "Group Policy" subkey that keeps track of information on the active GPOs. That last thing includes a "version" value, but it doesn't quite match with the version in gpt.ini, so I'm not sure what its significance is.

Saturday, August 13, 2016

Policy Plus - Edit policy settings

Today I made serious progress toward the goal of letting Policy Plus modify and save policy settings. I started by writing a function that does the jobs of finding all Registry entries touched by a policy and optionally blowing them away. I then wrote a function to write the appropriate Registry values for a given policy options configuration. Along the way, I had to fix some oversights, like IPolicySource not having a way to mark and unmark a key as being cleared, or the "forget this value" function not removing marks for value deletion.

With those changes in place, I updated the setting editor dialog to jam the UI state into one of those dictionaries and then call the thing that updates the policy. It seems to be working well.

The next thing to do is add a Save button to the main form that causes the diff-and-merge routine to execute, applying the changes to the Registry.

Thursday, August 11, 2016

AI Stack Exchange site

The Artifical Intelligence Stack Exchange site proposal started the commitment phase three months ago. It launched into private beta nine days ago and is now underway. So far, we have a pretty good amount of questions. Our scope is a little shaky (implementation questions are currently off-topic because they're better fit to other sites), but we're working on it. I have good hope for its future.

If you're interested in joining, visit the site, or add a comment here with contact info if you'd like an official e-mail invitation.

---

Update: We're in public beta! Browse around and jump in!

Wednesday, August 10, 2016

Policy Plus - Load extra options

Some time ago, I wrote the part of Policy Plus that creates all the UI elements for a given policy's extra options. Today I took on the challenging task of writing the functions that populate those UI elements. A new PolicyProcessing static method is responsible for producing one object for each ADMX element. The setting editor dialog then puts those objects into the appropriate UI elements.

Successfully loaded the policy's extra options
I also discovered a problem with the PolFile.GetValueNames implementation: it produced the internal names (which include both the key and value). I fixed it so it only returns the value names. (This matters when loading "list" policy elements.)

The changes are live on GitHub.

Tuesday, August 9, 2016

Policy Plus - Highlighted category information

Yesterday, I noticed that categories can have explanation texts too, so I adjusted the ADMX loading system to read them. Today, I changed the Policy Plus main form to display information about categories when they're highlighted in the listing of another category. The category name, description, and count of contents are displayed.

The "App runtime" category selected
In the process, I introduced (and published, whoops) a bug that caused a crash when highlighting a category without an explanation. VB.NET partially - but not completely - smooths over the difference between blank strings and null strings. Before publishing, I had changed the explanation trimming logic into a call to the instance String.Trim method, but that's a null reference exception if the string is null. I fixed it by having AdmxBundle.ResolveString return blank strings when given null strings.

There are only two more Hard Problems to solve that I can see: figuring out the state of all the extra options for policies, and saving changes to policies. I'll start on the former next.

Monday, August 8, 2016

Policy Plus - Find by ID

Online instructions frequently say to navigate through a moderately long path in the Group Policy category structure and then hunt down a specific policy. Policies and categories already have unique IDs (an internal name qualified by the ADMX's target namespace), so it would be expedient to refer to objects by those names.

Today I added a Find by ID option that works on categories and policies. Pressing Ctrl+G or picking the appropriate menu item brings up a small window where the desired object's ID can be typed or pasted in. It even has a little picture on the left that changes into a folder or page depending on whether the ID is for a category or policy.


I may extend this dialog to work for products as well once I get a browser for those.

Sunday, August 7, 2016

Policy Plus - Setting editor UI assembly

Yesterday, I forgot to mention that I also changed the State text calculation method's behavior when both user and computer policies are shown. Policies that only appear in one section now always show the section in the State column, even if they're Not Configured. Previously, such policies would show up as "Not Configured (2)" despite applying to only one section.

Notice the parenthesized section IDs
Then I continued adding the presentation elements to the dynamically generated setting editor UI. All element types are supported now, even <comboBox>, which isn't used and therefore isn't tested yet. Getting everything to line up and size properly was an immensely fiddly experience, especially with checkboxes that have text that might span multiple lines. Controls that have labels (texts that aren't actually separate elements) are added in FlowLayoutPanel instances. Fortunately, I don't have to deal with arbitrary on-the-fly resizing, so the size of stuff just has to be calculated once.

The setting editor dialog
Along the way, I found an omission or two in the loaded structures. Those are fixed now.

I got worried for a moment when I remembered the soft attribute (which is supposed to make the element's value not overwrite an existing Registry entry) because implementing it would require huge changes to my model, but then I saw that it's completely unused in the default ADMX files, probably because it's impossible to implement.

The changes are live on GitHub.

Policy Plus - Presentation time

The next step for Policy Plus is to have a way to actually change the policy settings. Today I started putting together the setting editor dialog. While doing that, I found that the presentation object wasn't attached to the compiled policy object, so I updated the various loaders slightly to fix that. There's also now an ElementType field on policy and presentation elements, which will help when the setting editor needs to create UI elements from them. (I have it working for plain labels so far.)

I also noticed that one policy showed up as a preference in Policy Plus but not in the real Group Policy Editor. Evidently, there are more locations for policies that I didn't know about, like System\CurrentControlSet\Policies. It's also possible that any key involving "Policies" counts; I'll have to look into that.

Some policies are apparently defined in their ADMX files as two separate but exactly identical policies - one for computers and one for users - despite there being an easy way to have a policy apply to both sections. I wrote a deduplicator menu option, and it works, but the way it rearranges things could make the development of future features more difficult, so this ability might get removed.

Friday, August 5, 2016

Policy Plus - Scrollbar finagled

After last night's post, I continued fiddling with Policy Plus's setting information panel scrollbar, and I got it into a pretty good state. A vertical scrollbar - and only a vertical scrollbar - appears when the pane's contents are too tall for it. I removed the horizontal scrollbar using some P/Invoke as suggested in this Stack Overflow answer.

When the vertical scrollbar appears, the text column narrows so it's not overlapped by the bar.

A working scrollbar appears in the middle pane
There is a little flicker in that pane now when the selected policy is changed. I'm also still experiencing a weird issue with a pointless and inert and mis-styled horizontal scrollbar appearing in the right pane when the window is maximized and restored, so the existing UI isn't yet perfect.

Wednesday, August 3, 2016

Policy Plus - POL diffing

Applying a policy file to the Registry is actually a two-step operation. One step is to be expected: take all the stuff in the current POL file and do the stuff it says to the Registry. Then, though, you have to compare the newest POL to the previous version, find the entries that are no longer present, and delete them from the Registry. Today I added a method to the PolFile class for doing just that: ApplyDiff. In the course of doing so, I also had to add a small method for checking whether a key is really a policy container (as opposed to one for non-removable preferences) by looking at its path. Those changes are live on GitHub.

Using that small function, I updated the UI just a little to flag the preference-ness of policies with an exclamation mark on the setting icon. Then I went to work on the lack of scroll bar for the policy description text area. It was shockingly difficult to get the scrollbars to only appear when needed and to make the text not get overlapped or cut off by the scrollbar when it did appear. I'm still working on getting rid of the completely unnecessary horizontal scrollbar for that pane. I'll push to GitHub again when that's ready.

Tuesday, August 2, 2016

FMod - Automated image export glitch

Yesterday I received a report of Abiathar's image exporter failing under some circumstances. Fortunately, the reporter included the exact configuration under which the problem occurred. I was able to reproduce the issue when I set an automatic filename pattern for the exported image. It only happened when the dependency file hadn't been saved. One of the things passed to the string formatter was the path of the dependency file, after going through some .NET IO functions to make sure it's a full rooted path.

When the dependency file is unsaved, the path to it is blank, and that causes those functions to fail, throwing an exception (even if the format string didn't use its results). I'll solve the problem by checking to see if the file is on disk before passing the path through those functions.

Monday, August 1, 2016

PowerShell script for automatically putting diffierent kinds of downloaded files in different places

Somebody asked for a way to automatically move things out of the Downloads folder into different places depending on what kind of file each is. I figured the easiest way to approach that is to just check for files there every so often and process each appropriately. I made this script:

$places = @{txt='?\Documents\TextFiles'; exe='?\Documents\NewApps'}

$userprof = [Environment]::ExpandEnvironmentVariables('%USERPROFILE%')
cd ($userprof + '\Downloads')
Do {
  dir -File | % {
    If ($_.Extension.Length -gt 0) {
      $ext = $_.Extension.Substring(1).ToLowerInvariant()
    } Else {
      $ext = 'NOEXT'
    }
    If (-not $places.ContainsKey($ext)) {$ext = 'DEFAULT'}
    If ($places.ContainsKey($ext)) {
      $dest = $places[$ext].Replace('?', $userprof)
      move $_ -Dest $dest
    }
  }
  Start-Sleep -Seconds 5
} While ($true)

The first line defines which file types should go where. A question mark in the destination is replaced with the path to the root of the user profile. Extensionless files will be processed by a NOEXT entry if present; files of types not present in that dictionary will be handled by the DEFAULT entry if it exists, otherwise they'll be left in place.

The script runs in the background and only wakes up every five seconds (as seen in the second to last line). That background task can be started with this batch command:

powershell -Command "Start-Process -WindowStyle Hidden -FilePath powershell -ArgumentList 'C:\path\to\your\script.ps1'"