PowerShell Set Drive Labels Persisting And Unchangeable Until Reboot

1.4k views Asked by At

Our software needs to map a network drive depending on which database the User logs in to.

The software first checks that the drive isn't already mapped, if it is then it skips the mapping step.

If the drive isn't mapped, or it is mapped to a different share (i.e. the User was previously logged in to a different database), then it clears any existing drive mapping, and maps the required drive.

It does this by generating and then running a PowerShell script.

Remove-SmbMapping -LocalPath "R:" -Force -UpdateProfile; 
Remove-PSDrive -Name "R" -Force; 
net use "R" /delete /y; 

$password = ConvertTo-SecureString -String "Password" -AsPlainText -Force; 
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "Username", $password; 
New-PSDrive -Name "R" -PSProvider "FileSystem" -Root "\\server\share" -Credential $credential -Persist;

$a = New-Object -ComObject shell.application;
$a.NameSpace( "R:" ).self.name = "FriendlyName";

The first three lines remove any existing mapping on that drive letter. They all theoretically do the same thing, however thanks to Microsoft it's entirely random which line will actually work. It only consistently works if all three lines are run.

The middle three lines map the new drive.

The last two lines change the drive label of the new drive to something more user-friendly than the default \\server\share label

The first time someone logs in after a reboot the above script works perfectly. The new drive is mapped, and the label is changed.

However, if the User then logs out and logs into a different database, the label will not change.

For example, the User first logs in to 'Database A', and the drive is mapped with the label 'DatabaseAFiles'. All well and good.

But if the User then logs out, and logs in to 'Database B', the drive is correctly mapped and points to the correct share, but the label still says 'DatabaseAFiles' and not 'DatabaseBFiles'.

If the User reboots their PC, however, and logs in to 'Database B', then the label will correctly say 'DatabaseBFiles', but any subsequent log ins to other databases again won't change the label.

Reboot
Log in to Database A, label is DatabaseAFiles
Log out and into Database B, label is still DatabaseAFiles
Reboot
Log in to Database B, label is now DatabaseBFiles

This is not dependent on the last two script lines being present (the two that set the label), I actually added those to try to fix this issue. If those two lines are removed, the label is the default \\server\share label, and still doesn't change correctly, i.e.

Reboot
Log in to Database A, label is \\servera\sharea
Log out and into Database B, label is still \\servera\sharea
Reboot
Log in to Database B, label is now \\serverb\shareb

Regardless of the label, the drive is always correctly mapped to the correct share, and using it has all the correct directories and files.

Everything works correctly, it's just the label that is incorrect after the first login per reboot.

The script is run from within a C# program in a created PowerShell instance

using (PowerShell PowerShellInstance = PowerShell.Create())
{
    PowerShellInstance.AddScript(script);

    IAsyncResult result = PowerShellInstance.BeginInvoke();

    while (result.IsCompleted == false)
    {
        Thread.Sleep(1000);
    }
}

As it maps a drive, it cannot be run in Adminstrator mode (the drive won't be mapped for the actual User), it has to be run in normal mode, so there is a check earlier up for that.

If I take a copy of the script and run it in a PowerShell session outside the C# program, I get exactly the same results (everything works but the label is wrong after the first login), so it's not that it's being run from within the C# program.

It's entirely possible that the issue is with either File Explorer or with Windows, either caching the label somewhere and reusing it could be the problem, of course.

Anyone have any suggestions of things I can try please?

2

There are 2 answers

0
LMS On

It's not ideal, there's one bit I'm not happy about, but this is the best I've been able to come up with so far. If I come up with something better I'll post that instead.

Firstly, I check if there is already a drive mapped to the letter I want to use:-

// Test if mapping already exists for this database
var wrongMapping = false;
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
    var driveLetter = drive.RootDirectory.ToString().Substring(0, 1);
    if (driveLetter == mappingDetails.DriveLetter && Directory.Exists(drive.Name))
    {
        wrongMapping = true; // Assume this is the wrong drive, if not we'll return from the method before it's used anyway
        var unc = "Unknown";
        using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + driveLetter))
        {
            if (key != null)
            {
                unc = key.GetValue("RemotePath").ToString();
            }
        }
        if (unc == mappingDetails.Root)
        {
            View.Status = @"Drive already mapped to " + mappingDetails.DriveLetter + ":";
            ASyncDelay(2000, () => View.Close());
            return; // Already mapped, carry on with login
        }
    }
}

If we already have the correct path mapped to the correct drive letter, then we return and skip the rest of the mapping code.

If not, we'll have the variable wrongMapping, which will be true if we have a different path mapped to the drive letter we want. This means that we'll need to unmap that drive first.

This is done via a Powershell script run the by C# program, and contains the bit I'm not happy about:-

Remove-PSDrive mappingDetails.DriveLetter;
Remove-SmbMapping -LocalPath "mappingDetails.DriveLetter:" -Force -UpdateProfile;
Remove-PSDrive -Name "mappingDetails.DriveLetter" -Force;
net use mappingDetails.DriveLetter /delete /y;
Stop-Process -ProcessName explorer;

The first four lines are different ways to unmap a drive, and at least one of them will work. Which one does work seems to be random, but between all four the drives (so far) always get unmapped.

Then we get this bit:

Stop-Process -ProcessName explorer;

This will close and restart the Explorer process, thus forcing Windows to admit that the drive we just unmapped is really gone. Without this, Windows won't fully release the drive, and most annoyingly it will remember the drive label and apply it to the next drive mapped (thus making a mapping to CompanyBShare still say CompanyAShare).

However, in so doing it will close any open File Explorer windows, and also briefly blank the taskbar, which is not good.

But, given that currently no Company sites have more than one share, and it's only the Developers and Support that need to remove existing drives and map new ones, for now we'll put up with it.

Once any old drive is unmapped, we then carry on and map the new drive, which again is done via a PowerShell script run from the C# code.

$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;
$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';
New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";

The first part maps the drive:

$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;

The middle part changes the name directly:

$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';

And the end part changes the name in the Registry:

New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";

Firstly, it creates a key for this path (it the key already exists it'll fail but the script will carry on)

Then it removes the existing property _LabelFromReg (if it doesn't exist it'll fail but the script will carry on)

Then it (re)creates the property _LabelFromReg with the new friendlyname.

So, again doing the same thing two ways, but between the two it works.

I'd like to find some alternative to having to kill and restart the Explorer process, it's really tacky, but it seems to be the only way to get Windows to acknowledge the changes.

And at least I now get the correct labels on the drives when mapped.

1
f6a4 On

A time ago, I have had to rename file shares and therefor I wrote this function. Maybe this is helpful for you.

#--------------------------------------
function Rename-NetworkShare {
#--------------------------------------

    param(
        [string]$sharePattern,
        [string]$value
    )

    $regPath      = Get-ChildItem 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2'

    $propertyName = '_LabelFromReg'

    foreach( $child in $regPath ) {

        if( $child.PSChildName -like ('*' + $sharePattern + '*') ) {

            if( !$child.Property.Contains( $propertyName ) ) {
                New-ItemProperty $child.PSPath -Name $propertyName -PropertyType String | Out-Null
            }
            Set-ItemProperty -Path $child.PSPath -Name $propertyName -Value $value | Out-Null
        }
    }
}

Rename-NetworkShare -sharePattern 'patternOldName' -value 'NewFriendlyName'