Blog

QNAP TS-451+

I bought a QNAP TS-541+ NAS. It came with 8 GB RAM and I added an 8 TB hard drive and a 500 GB SSD.

After downloading the QFinder tool the NAS configured itself and finally showed this Web interface.

desktop

The interface is quite snappy and works really really well. In fact I haven't seen a Web interface as well-done as this ever before. (Citrix XenApp comes close but is not nearly as pretty.)

controlpanel

So far I found you can create users and groups, shares (SMB, AFP, and NFS), and an FTP server. The NAS also acts as a Web server and can be a VPN server or even a (apparently Windows-compatible) domain controller.

Each user has a home directory. Only the "admin" user can log in via ssh (or Telnet, if enabled). The su and sudo commands are missing.

storagemanager

The NAS also supports the creation of virtual machines. I think it uses KVM.

virt


There are a lot of apps and features I didn't even try or look at yet.

So far I recommend the device fully.

Could not connect to one or more vCenter Server systems

Very very very often vCenter 6.5 will greet me with the helpful message "Could not connect to one or more vCenter Server systems". It turns out this is a bug in vCenter 6.5.

To recover vCenter, I had to follow these simple few steps.

1. Secure shell into the vCenter appliance and open the vpxd log file.

/var/log/vmware/vpxd/vpxd.log

2. In it, find error messages at the end that look like this.

error vpxd[7F0A533E7700] [Originator@6876 sub=vpxCommon opID=HostSync-host-19-795056c4] [Vpxd_HandleVmRootError] Received unrecoverable VmRootError. Generating minidump ...

error vpxd[7F0A533E7700] [Originator@6876 sub=Default opID=HostSync-host-19-795056c4] An unrecoverable problem has occurred, stopping the VMware VirtualCenter service. Error: Error[VdbODBCError] (-1) "ODBC error: (23505) - ERROR: duplicate key value violates unique constraint "pk_vpx_vm_virtual_device";

--> Error while executing the query" is returned when executing SQL statement "INSERT INTO VPX_VM_VIRTUAL_DEVICE (ID, DEVICE_KEY, CONN_AL_GU_CONTROL_FLAG, CONN_CONNECTED_FLAG, CONN_START_CONNECTED_FLAG, CONN_STATUS, DEVICE_INFO_SUMMARY, DEVICE_INFO_LABEL, DEVICE_CONTROLLER_KEY, DEVICE_UNIT_NUMBER, DEVICE_TYPE, DEVICE_TEXT) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"

3. Open the postgresql log file (use ls -trl to find the current one, check the last two or three).

/var/log/vmware/vpostgres

4. Find the corresponding (to the above errors) entry. It looks like this.

VCDB vc ERROR:  duplicate key value violates unique constraint "pk_vpx_vm_virtual_device"

VCDB vc DETAIL:  Key (id, device_key)=(101, 3002) already exists.

VCDB vc STATEMENT:  INSERT INTO VPX_VM_VIRTUAL_DEVICE (ID, DEVICE_KEY, CONN_AL_GU_CONTROL_FLAG, CONN_CONNECTED_FLAG, CONN_START_CONNECTED_FLAG, CONN_STATUS, DEVICE_INFO_SUMMARY, DEVICE_INFO_LABEL, DEVICE_CONTROLLER_KEY, DEVICE_UNIT_NUMBER, DEVICE_TYPE, DEVICE_TEXT) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)

5. Make a not of the id and device_key (here 101 and 3002).

6. Start the psql client and connect as the postgres user. (Apparently no password is needed.)

/opt/vmware/vpostgres/current/bin/psql -U postgres

7. Switch to the VMware database. Note the case sensitivity.

\c VCDB

8. Show the offending device entry. Use the ID and DEVICE_KEY found above.

SELECT * FROM vpx_vm_virtual_device WHERE ID=101 AND DEVICE_KEY=3002;

9. If the device looks like it could be the offender (perhaps it is an externally connected USB device), go ahead and delete it (but don't blame me if this goes horribly wrong).

DELETE FROM vpx_vm_virtual_device WHERE ID=101 AND DEVICE_KEY=3002;

10. Reboot the vCenter appliance.


Also see http://vman.ch/vcenter-vdbodbcerror-unique-constraint-pk_vpx_vm_virtual_device-fix/.





Visual Studio 2017 First Look

Visual Studio 2015 used up lots of disk space, all on drive C:, regardless of where and what one chose to install it. Visual Studio 2017 is a lot more modular. Visual Studio 2017 also appears to have integrated Xamarin as well as third-party tools.


01windows

The Windows section of the installer offers three ways to develop for Windows.


02universalwindows

Universal Windows Platform development targets Windows 10, Windows Server 2016 Desktop Experience, Windows 10 IoT Core, and Windows 10 Mobile. It supports both .NET languages and C++. I am guessing Windows 8 support is long forgotten for good reason.


03desktopcpp

Desktop development with C++ targets all versions of Windows that support Win32 applications down to Windows XP.


04desktopcsharp

.NET desktop development targets all versions of Windows that support .NET Framework 4.0 and above, i.e. Windows Server 2003 and Windows Vista and above. It does not target but can also be used for versions of Mono that support .NET Framework 4.0 and above on Mac OS, GNU/Linux and other platforms supported by Mono.


05web

The Web & Cloud section offers a motley collection of features, some of which target the Web, some of which target Microsoft's cloud Azure, and some of which target all sorts of other things.


06aspnet

ASP.NET and web development provides a Web framework for .NET. It targets Internet Information Server on Windows. It does not specifically target but might work with Mono.


07nodejs

Node.js development appears to be a JavaScript environment of some kind.


08office

Office/SharePoint development contains tools for Office and SharePoint development, which Microsoft apparently consider "Web" and "Cloud".


09azure

Whereas Azure development actually is Microsoft's cloud and Visual Studio provides utilities and build tools.


10data

And I assume Data storage and processing is a collection of programming tools for SQL Server and Azure Data Lake.


11mobile

Mobile & Gaming provides tools for mobile and game development, as the name might suggest.


12xamarin

Mobile development with .NET is really Xamarin and contains .NET libraries to support statically-linked targeting (with the Xamarin libraries) of iOS, Android and Universal Windows Platform.


13javascript

Mobile development with JavaScript I guess supports mobile development with JavaScript using Apache Cordova.


14game

Game development with C++ contains C++ libraries for game development for DirectX and the Unreal engine.


15unity

Game development with Unity contains Visual Studio support for the Unity game development framework for .NET.


16mobilecpp

Mobile development with C++ targets native iOS and the Android NDK.


17other

Other Toolsets is everything Microsoft didn't want to sort into the above categories, although it would fit.


18extensions

Visual Studio extension development is the SDK for Visual Studio itself.


19netcore

.NET Core cross-platform development targets all .NET Core platforms including Windows 10, Windows Server 2016, Windows Server 2016 Server Core, Windows Server 2016 Nano Server, Windows 10 IoT Core, Windows 10 Mobile, GNU/Linux and Mac OS. It presumably also works with Mono platforms.


20linuxcpp

Linux development with C++ targets GNU/Linux using a Microsoft compiler and C runtime library.


All-in-all Visual Studio 2017 appears to be a return to CP/M times at Microsoft. Instead of supporting Microsoft platforms only, Microsoft have returned to trying to develop the best developer tools for Microsoft and non-Microsoft platforms. (After all, Microsoft have started as a developer tools company.)

Microsoft do not provide an ISO or even an offline installer. But such a beast can be created by running the provided online installer in a special mode.

Microsoft explain here how to create an offline installer for Visual Studio 2017.

tl;dr vs_enterprise.exe --layout C:\vs2017offline --lang en-US creates a Visual Studio 2017 offline installer in c:\vs2017offline. Replace vs_enterprise.exe with the appropriate name for the installer you have.



IBM PC Boot Loader - Reading the Root Directory

A FAT12 file system's directory consists of a number of 32 bit long entries. The first 11 characters of each entry are the file name (8 characters) and file type (3 characters).

Mounted as a -t msdos in Linux it looks like this.

lsl

On the disk, as seen in a hex editor, it looks like this.

rootdir1

Note that file names shorter than 8 characters are actually 8 characters long with the missing characters filled with spaces (ASCII 0x20).

Returning to the existing boot loader, with the variable names updated because I felt like it. This is the new beginning of the boot sector.

init

As noted in earlier blog entries this code is loaded at address 0x7C00. The root directory has to be loaded somewhere too (and later the FAT). There is also need for a stack segment (which here starts at 0x6C00 some 4 KB before the code).

locations

Remember that segment registers are extended by a nybble (half a byte). Hence 0x6C0 in a segment register equals the segment base address 0x6C00. (In order not to be confused I start the names of offsets with a "p" and the names of segment addresses with "a".)

The boot loader main routine configures the segment registers (code = data = extra, stack at 0x6C00) and defines a stack of 4 KB. It then proceeds to print "hello" before calling the loaddir and findfile routines and ending the program with printing "bye" before rotating forever.

start

The loaddir routine loads the disk's root directory at address 0x7E00. The root directory is 14 sectors long and starts at sector 19.

The IBM 3.5" 1.44 MB floppy disk has 80 tracks and 18 sectors per track. (It also has two sides, if you can believe it.) The fact that the root directory is located right at the beginning of a track and is shorter than the track allows reading it with little calculation and in one move.

Sector 19 is the second sector of the second side of the first track.

Tracks are zero-based. Hence the first track is track 0. This goes into CH.

Sectors are one-based. Hence the second sector is sector 2. Goes into CL.

Disk sides are zero-based. Hence the second side is side 1. Goes into DH.

There is only one drive. Drives are zero-based. Drive 0 goes into DL.

This routine uses hard-coded block addresses. (I think this is appropriate since the geometry of the disk was known when the boot sector was written. It's also easier to write. The routines for calculating logical block addresses from physical addresses and vice versa explained in a previous entry are not being used.)

loadrootdir

After loading the root directory at address 0x7E00 each entry needs to be read and compared to the name of the file that needs loading. This is as far as this goes for now.

findfile1

This routine displays the file name and type it finds when comparing each directory entry with the image file name. (Ultimately it should of course load the file and far jump into the newly loaded code.)

The accumulator (AX) is initialised with 0 to be used as an offset, the counter (CX) with the number of root directory entries (224). The source index (SI) is initialised with the address of the image file name and the destination index is initialised with the address of the root directory in memory (0x7E00).

At this point the loop starts.

The current directory entry is at DI+AX. The relevant part of the entry is the first 11 bytes, the file name (8 bytes) and the file type (3 bytes). This is compared with the image file name. The string comparison is done using a special x86 instruction for string comparisons that uses the SI and DI registers (which are already filled correctly) and the CX register (which therefor has to be saved before and restored after the string comparison). The string comparison is to be done on 11 characters, hence CX is initialised with 11.

If the two 11-character strings are equal, the file is found and its name displayed on the screen. There happens to be a zero following the file name. This is because byte 11 is the attribute byte and there are no attributes set. I am using this fact so I can use my writes routine which prints zero-terminated strings.

When running, it looks like this.

os86

Note that there is no carriage return and line feed following the file name and file type output.

To be continued...

Data Types and Pointer Sizes

Because I was bored and because boring repetetive tasks interest me greatly, I wrote a little program that displays data type sizes and compiled it for a variety of platforms immediately available to me.

This is the program.

<p>
#include <stdio.h>

int main()
{
    char *s = ""
        "\nType sizes of current architecture and system\n"
        "char: %d\n"
        "short: %d\n"
        "int: %d\n"
        "long: %d\n"
        "long long: %d\n"
        "float: %d\n"
        "double: %d\n"
        "long double: %d\n"
        "pointer: %d\n\n";
    printf(s,
        sizeof(char)*8,
        sizeof(short)*8,
        sizeof(int)*8,
        sizeof(long)*8,
        sizeof(long long)*8,
        sizeof(float)*8,
        sizeof(double)*8,
        sizeof(long double)*8,
        sizeof(char *)*8
    );
}
</p>

It does absolutely nothing useful, only displays the sizes, in bits, of various data types (the last one being a char pointer).

The first three compiled versions were for 32 bit Windows NT (x86 and ARM) and 64 bit Windows NT (AMD64).

SizeTest.Win32.i386


SizeTest.Win32.ARM

The first binary tells us that in 32 bit Windows NT (Windows 10 in this case), a byte is 8 bits long (this is true for all the platforms involved here), an int is a long is 32 bits and a long long is 64 bit. Pointer size of 32 bit Windows NT is, you guessed it, 32 bits.

Windows NT on ARM works exactly the same way. 8 bit bytes, 16 bit shorts, 32 bit ints and longs and 64 bit long longs plus 32 bit pointers.

Note the 64 bit long double floating point type which is identical with the 64 bit double type for all Windows platforms.

SizeTest.Win32.amd64

64 bit Windows NT simply has a greater pointer size, the most unexpected 64 bit for the 64 bit operating system.

This is called the LLP64 or IL32P64 data model and is used by Windows NT on AMD64 and Itanium.


But on the other hand it should be noted that UNIX, represented here by the favourite Unix clone GNU/Linux, uses 64 bit longs (and 128 bit long doubles). Pointer size is also 64 bit. This is called the LP64 or I32LP64 data model and is used by various operating systems including most UNIX derivatives and clones.


Back in the PC world 32 bit OS/2 just like 32 bit Windows NT. This is of course not a coincidence. Windows NT started its career as Microsoft's planned replacement for OS/2.

SizeTest.OS2.i386

OS/2 was a weird 16-32 bit hybrid and also supported 16 bit software.

SizeTest.OS2.i286

A 16 bit OS/2 executable lives in a 16 bit world, with 16 bit ints and 16 bit pointers. This is a segmented architecture. While pointers were 16 bit it was possible to address significantly more memory than 64 KB in total.

Note the traditional Windows and OS/2 identity of double and long double.

MS-DOS (note the 8.3 file name) provided (or existed in) a similar environment. The difference was that OS/2's memory addresses were not real. Again this is a segmented architecture.

SizeTest




OpenVMS on Alpha uses the same data model as Windows NT. 64 bit VMS appeared before Windows NT which at the time ran on the Alpha CPU but in 32 bit mode.

But note that OpenVMS does not adhere to the Windows and OS/2 tradition of identical double and long double types.

SIZETEST-VMS-AXP64

It is possible on OpenVMS to create a 32 bit executable. This doesn't seem to impact the other data types. I am not sure what it really means.

SIZETEST-VMS-AXP32

Note the fact that OpenVMS file names are case-insensitive and cannot contain dots. (Like in MS-DOS the dot separates the file name from the file type specifier. This is actually a good idea.)

VereMolf

I want to introduce my friend VereMolf.

VereMolf

Further details might follow.

Windows Registry Keys - A Growing List

This blog entry is the beginning (and continuation) of a growing list of Windows Registry Keys about which I had reason to know or in which I had to modify values.

I try to keep them sorted in roughly alphabetical order, not counting \WOW6432Node.

HKLM:\SOFTWARE\

  • HKLM:\SOFTWARE\WOW6432Node\Citrix\ICA Client
  • REG_DWORD VdLoadUnLoadTimeOut
  • VdLoadUnLoadTimeOut sets a connection timeout in seconds. Set to something large (10 seconds) to avoid "This version of Citrix Receiver does not support selected encryption" errors which are apparently triggered by Receiver deciding that that must be the case for the connection not finishing. (Found in Citrix Knowledge Center CTX133536.) Why this is a hard-to-find registry value and not simply enabled by default is a Citrix mystery.

  • HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\
  • REG_DWORD EnableLUA
  • Configures Limited User Account (User Account Control). 0 disables LUA, 1 enables LUA. Explained here in the Microsoft Developer Network. Check that article for further options.

  • HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting
  • REG_DWORD Disabled
  • Set to 1 to disable Windows Error Reporting. (That's the annoying discussion Windows starts after an application crashes.)

  • HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList
  • REG_DWORD UserName
  • UserName can be any user name. Set its data to 0 to hide the user from the login screen. (You can still log on as that user by typing his name.)

  • HKLM:\Software\Policies\Microsoft\Windows\Explorer
  • REG_DWORD DisableNotificationCenter
  • Set to 1 to disable the annoying "Notification Center".

  • HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU\
  • REG_DWORD AUOptions
  • Configures Windows Update. (Explained here in the Microsoft Developer Network.) 1 actually disables automatic updates, This allows users to choose themselves when to check for and download updates.

HKLM:\SYSTEM\

  • HKLM:\SYSTEM\CurrentControlSet\Control
  • REG_DWORD ServicesPipeTimeout
  • Apparently this configures the time services have to react and start. Default might be 30 seconds (30000 decimal). Given in milliseconds.


Windows Account Tokens

In Windows NT user rights are defined by access permissions (defined in ACLs on file system and other objects) and privileges (defined for user accounts). Privileges override permissions.

Privileges for a a user account are defined in an account token. Specifically, they are one kind of TokenInformation which can be of different kinds. The different kinds of TokenInformation object are defined in the TOKEN_INFORMATION_CLASS enumeration and are gotten using the GetTokenInformation function for the token. The kind of TokenInformation needed here is TokenPrivileges (one of the values of TOKEN_INFORMATION_CLASS).

The TokenPrivileges come in structure TOKEN_PRIVILEGES. TokenPrivileges contain a number of privileges.

Each privilege is a LUID_AND_ATTRIBUTES structure consisting of, you will love this, a LUID and a 32 bit value Attributes. The LUID is a structure containing two 32 bit values representing lower and upper doublewords of a 64 bit value (a quadword). (Windows NT defines a "word" as a 16 bit value, even on 32 bit and 64 bit systems.)

The LUID encodes the privileges, apparently on a per-system basic (as the LUID is unique for each system). The Attributes define whether the privilege is enabled or used or whatever else might be encoded there because all the documentation I could find was another MSDN entry for LUID_AND_ATTRIBUTES in the MSDN hardwars section.

SE_PRIVILEGE_ENABLED

The privilege is enabled. 

SE_PRIVILEGE_ENABLED_BY_DEFAULT

The privilege is enabled by default. 

SE_PRIVILEGE_USED_FOR_ACCESS

The privilege was used to gain access to an object or service. This flag is used to identify the relevant privileges in a set passed by a client application that may contain unnecessary privileges. 

The LookupPrivilegeName function returns the (readable) names of a token's privileges encoded in the token's LUID.

I wrote a crude test program that displays the privileges of a user.

It can display privileges for the current local user or for any domain user.

Specifically, for my local user (who is an administrator), it displays this:

localpriv

When started without elevation, it looks like this:

notelev

(So this is what User Account Control does to honest people!)

It really doesn't work for other local users.

So I decided to try out my little program with a domain user.

storefront

My domain user isn't an administrator.

domainpriv

But I can see the privileges of other users:

hubertpriv1

User "hubert" has the same privileges as my user.

Running the program against the domain administrator account crashes the program because the domain administrator holds vastly more privileges than my 1000-item array can handle. (Yes, its size can be increased.) Running the program as domain administrator returns the same 23 privileges held by my local administrator account outside the domain.


Related links

Fixing Windows 10 Version 1607 Internet Sharing

Since Internet Sharing stops working after a reboot (not just during) with the current version of Windows 10, a Scheduled Task is necessary to restart Internet Sharing at boot.

Use this script to start Internet Sharing.

param(
 $sPublicAdapterName,
 $sPrivateAdapterName
)

if (!$sPrivateAdapterName) {
 Write-Host
"EnableSharing.ps1 sPublicAdapterName sPrivateAdapterName"
 return
}#if

# Constants
$public = 0
$private = 1

Write-Host "Creating netshare object..."
$netshare = New-Object -ComObject HNetCfg.HNetShare

Write-Host "Getting public adapter..."
$publicprops = $netshare.EnumEveryConnection | Where-Object {
 
$netshare.NetConnectionProps($_).Name -eq $sPublicAdapterName
}#foreach
$publicadapter = $netshare.INetSharingConfigurationForINetConnection($publicprops)
$publicadapter | more

Write-Host "Getting private adapter..."
$privateprops = $netshare.EnumEveryConnection | Where-Object {
 
$netshare.NetConnectionProps($_).Name -eq $sPrivateAdapterName
}#foreach
$privateadapter = $netshare.INetSharingConfigurationForINetConnection($privateprops)
$privateadapter | more

Write-Host "Disabling public sharing for public adapter..."
$publicadapter.DisableSharing()
$publicadapter | more

Write-Host "Disabling private sharing for private adapter..."
$privateadapter.DisableSharing()
$privateadapter | more

Write-Host "Enabling public sharing for public adapter...."
$publicadapter.EnableSharing($public)
$publicadapter | more

Write-Host "Enabling private sharing for private adapter...."
$privateadapter.EnableSharing($private)
$privateadapter | more

# Clean up
Remove-Variable netshare

End script. Note that the exact order of disabling and enabling sharing does matter. (Trust me.)

Make sure your adapter names do not contain brackets or, possibly, other special characters. (Rename them if necessary.)

Use a batch file containing the below line to start it from the Task Scheduler.

powershell.exe -Command c:\Windows\Scripts\EnableSharing.ps1 PublicAdapter PrivateAdapter

PublicAdapter and PrivateAdapter are the two adapters. (PublicAdapter is the one connected to the Internet. PrivateAdapter is the one connected to your network.)

Using Puppet to Tame Windows 10

In an earlier blog entry I discussed Puppet and how to create a user for Puppet to run under.

I use Puppet to fix problems with new Windows 10 computers and VMs. The core of the Puppet configuration is a PowerShell script which is deployed and runs on the nodes.

The default node in the site.pp file imports a class configuration which controls the PowerShell script.

node default {
  class { 'configuration': }
  #other stuff
}#node

The site.pp file of the configuration module defines the configuration class. Among other things it defines a class ntrights, a class basic and a class configure_puppet_windows_user (see Configure Puppet User via Puppet).

class configuration {
  class { 'configuration::basic': }
}#class

The class basic runs a basic configuration script.

class configuration::basic {
  if $operatingsystem == 'windows' {
    file { 'C:\Windows\Temp\BasicConfiguration.ps1':
      ensure => file,
      source_permissions => ignore,
      source => 'puppet:///files/BasicConfiguration.ps1',
      before => Exec['basic_configuration'],
    }#file
    exec { 'basic_configuration':
      require => File['C:\Windows\Temp\BasicConfiguration.ps1'],
      command => 'C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy remotesigned -file C:\Windows\Temp\BasicConfiguration.ps1',
    }#exec
  }#if
}#class

And this is a version of the BasicConfiguration.ps1 script:

# Allow PowerShell scripts
Set-ExecutionPolicy "Unrestricted"

# Disable notifications
Set-Service "wscsvc" -StartupType "Automatic"
$pathExplorerRegistry = "HKLM:\Software\Policies\Microsoft\Windows\Explorer"
if (!(Test-Path $pathExplorerRegistry)) {
New-Item -ItemType "Directory" $pathExplorerRegistry
}#if
Set-ItemProperty $pathExplorerRegistry "DisableNotificationCenter" 1

# Disable automatic updates
$pathWindowsUpdateAU = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU\"
if (!(Test-Path $pathWindowsUpdateAU)) {
New-Item -ItemType Directory $pathWindowsUpdateAU -Force
}#if
Set-ItemProperty $pathWindowsUpdateAU "AUOptions" 1
$cs = Get-WmiObject Win32_ComputerSystem
if (!($cs.PartOfDomain)) {
Set-Service "wuauserv" -StartupType "Automatic"
}#if

# Disable error reporting
$pathErrorReporting = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting"
Set-ItemProperty $pathErrorReporting "Disabled" 1

# Disable Firewall
Get-NetFirewallProfile | Where-Object {$_.Name -eq "Domain"} | Set-NetFirewallProfile -Enabled "False"
Get-NetFirewallProfile | Where-Object {$_.Name -eq "Private"} | Set-NetFirewallProfile -Enabled "False"

# Enable search service
$sSearchService = "wsearch"
if (!((Get-Service $sSearchService).Status -eq "Running")) {
Set-Service $sSearchService -StartupType "Automatic"
Start-Service $sSearchService
}#if

# Install root certificate if file exists
$pathRootCertificate = "C:\Windows\Temp\SomeCert.cer"
if (Test-Path $pathRootCertificate) {
$pathRootCertificateStore = "Cert:\LocalMachine\Root\"
Push-Location
Set-Location $pathRootCertificateStore
Import-Certificate $pathRootCertificate
Pop-Location
}#if

# Map public drive for all users
$pathCurrentVersionRun = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\"
$pathCurrentVersionRunOnce = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce\"
New-ItemProperty $pathCurrentVersionRunOnce -Name "RemovePublicDrive" -Value "net use p: /delete" -PropertyType "string"
New-ItemProperty $pathCurrentVersionRun -Name "MapPublicDrive" -Value "net use p: \\MyServer\public /persistent:yes" -PropertyType "string"

I hope this helps someone. Modify as required.


 © Andrew Brehm 2016