Bart Simons

powershell

A 5 post collection


Export Windows drivers with PowerShell

 •  Filed under export, windows, drivers, powershell

Need to reinstall Windows, but don't want to go through the hassle of getting all the needed drivers onto your new Windows installation? There's an easier way to work around that by using PowerShell! Just two cmdlets Get-WindowsDriver and Export-WindowsDriver are needed for this operation.

Obtaining a list of drivers is the first thing to do. You can use the Online and All flags to get a list of all the drivers from the currently running operating system. We put the output into a variable to be able to filter stuff later on:

$Drivers = Get-WindowsDriver -Online -All

Let's see out of which objects the variable exists so we can apply filters on this object:

PowerShell Driver Object

We can tell now that it's possible to filter on

  • Driver name;
  • Driver storage location and name;
  • Driver provider/manufacturer name;
  • Driver class/type;
  • Driver version;

You can also filter on date value, inbox value and boot criticality but it doesn't really bother me that much. If I want to fetch device drivers from a specific manufacturer for example, I can use this command:

$Drivers | where { $_.ProviderName -like "NVIDIA" }

Or if I want to list all types of drivers, I can use this:

$Drivers.ClassName | Sort-Object | Get-Unique

What if I wanted to get a list of all my network drivers?

$Drivers | where { $_.ClassName -like "*Net*" }

In case you need to export your drivers for e.g. migration purposes, this is what you want to run:

Export-WindowsDriver -Online -Destination "C:\Drivers"  

Especially the last command seems very useful to me. Next time I have to reinstall Windows, I know what to do 😉

Export And Import PowerShell Credentials

 •  Filed under powershell, credentials, export, import

PowerShell Credentials are very useful in the world of system administration. Passwords stored in a PSCredential object are encrypted by a key, so that means you can't just pipe a PSCredential into Export-Clixml to export your credentials because the key is stored in memory for the current user on the current computer only. I have written two functions to work around this by bundling a separate key and a linked PSCredential object into a PSObject, so you can export that object by piping it to Export-Clixml. You can then transfer that object to a remote computer and/or different user and import it via my second cmdlet listed below:

Export a credential with Export-Credential:
<#  
    .SYNOPSIS
        Exports a PowerShell credential the proper way.
    .DESCRIPTION
        Creates a PowerShell credential and returns the secured credential with it's key.
    .LINK
        https://bartsimons.me
#>

Function Export-Credential {  
    [CmdletBinding()]
    [OutputType(
        [PSCustomObject]
    )]

    Param (
        [Parameter(
            Mandatory = $false
        )]
        [SecureString]
        $Password,

        [Parameter(
            Mandatory = $false
        )]
        [String]
        $Username,

        [Parameter(
            Mandatory = $false
        )]
        [PSCredential]
        $Credential
    )

    If (-not ($Credential -and $Password)) {

        $CredentialSpecified = $false
        $PasswordSpecified   = $false

        $Key = New-Object Byte[](32)

        $Rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
        $Rng.GetBytes($Key)

        If ($Credential) {
            $CredentialSpecified = $true

            return New-Object PSObject -Property @{
                Username = $Credential.UserName
                Password = ConvertFrom-SecureString -SecureString $Credential.Password -Key $Key
                Key = $Key
            }
        }

        If ($Password -and $Username) {
            $PasswordSpecified = $true

            return New-Object PSObject -Property @{
                Username = $Username
                Password = ConvertFrom-SecureString -SecureString $Password -Key $Key
                Key = $Key
            }
        } Else {
            Write-Error "You need to specify both a username and password."
        }

    } Else {
        Write-Error "You can only specify a credential OR a password, not both"
    }
}
Import a credential with Import-Credential:
<#  
    .SYNOPSIS
        Imports a PowerShell credential the proper way.
    .DESCRIPTION
        Creates a new PowerShell credential from imported data.
    .LINK
        https://bartsimons.me
#>


Function Import-Credential {  
    [CmdletBinding()]
    [OutputType(
        [PSCustomObject]
    )]

    Param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [PSCustomObject]
        $Data
    )

    If ($Data.Username -and $Data.Password -and $Data.Key) {
        return New-Object System.Management.Automation.PSCredential($Data.Username, (ConvertTo-SecureString -String $Data.Password -Key $Data.Key))
    } Else {
        Write-Error "Inconsistent data structure in variable"
    }
}

An example:

# Generate a credential and export it to credentials.xml
Export-Credential -Credential (Get-Credential) | Export-Clixml credentials.xml

# Import the credential on the remote machine into the variable $theCredential
$theCredential = (Import-Clixml credentials.xml | Import-Credential)

I hope you find these two functions useful! Be careful though, anyone with access to your key could potentially decrypt your password. So think twice when you decide where to store your exported credentials 😉

Windows 10 Lock Screen Wallpapers On Your Desktop

 •  Filed under windows 10, spotlight, lock screen, wallpapers, on desktop, powershell

If you use Windows 10, you probably know about Windows Spotlight, the feature that automatically fetches beautiful wallpapers for on your lockscreen. But, we're not left out with any option to set the desktop wallpaper to Windows Spotlight photos by default! Luckily, I've written a really nifty little PowerShell script that actually gets the job done.

<# LockScreenWallpaperToDesktop.ps1 #>

$SystemWallpaperCode = @"
using System.Runtime.InteropServices;  
public class Wallpaper  
{
    public const int SetDesktopWallpaper = 20;
    public const int UpdateIniFile = 0x01;
    public const int SendWinIniChange = 0x02;
    [DllImport(`"user32.dll`", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni);
    public static void SetWallpaper ( string path )
    {
        SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
    }
}
"@

Add-Type -TypeDefinition $SystemWallpaperCode

$MinimumFileByteSize = 320000

$LockScreenWallpapersLocation = "$env:LOCALAPPDATA\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets"

$WallpaperFile = (Get-ChildItem $LockScreenWallpapersLocation | where { $_.Length -gt $MinimumFileByteSize } | Get-Random)[0]

$wallpaperFile=$WallpaperFile.Name

Copy-Item "$LockScreenWallpapersLocation\$wallpaperFile" "$env:USERPROFILE\wallpaper.png"

[Wallpaper]::SetWallpaper("$env:USERPROFILE\wallpaper.png")

Create GUI windows with PowerShell and XAML

 •  Filed under powershell, gui, xaml, scripting

GUI windows bring modern ways of interaction to your application and that is simply great: daily used applications like web browsers and e-mail client are all part of it. PowerShell has been proven to be great for automating tasks, but don't you sometimes want to bring GUI interaction to your script?

Visual Studio has great built-in functionality to design and create XAML layouts for your application:

XAML Designer in VS

The workflow is easy: just fill your form with items from the toolbox and copy all the XML code inside the XAML text editor. Start with a new PowerShell file,

Start with a new PowerShell file, and start with pasting the XML inside an PowerShell XML object like this:

[xml]$XAMLMain = @'
<Window  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Demo application" Height="350" Width="525">
        <Grid>
            <Label x:Name="label" Content="PowerShell + XAML demo application." HorizontalAlignment="Center" Margin="38,10,37.4,0" VerticalAlignment="Top" RenderTransformOrigin="1.056,1.635" Cursor="" FontSize="24" FontWeight="Bold"/>
            <Border BorderBrush="Black" BorderThickness="1" Height="238" Margin="20,57,20,0" VerticalAlignment="Top"/>
        </Grid>
</Window>  
'@  

The code copied from Visual Studio also contains the following:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
xmlns:local="clr-namespace:WpfApplication2"  
mc:Ignorable="d"  

One can strip these tags out of the copied code safely, the script won't run with this code included, so that's why this is necessary.

Next up is creating the code that launches the form. Append the following code to the end of the PowerShell file:

$reader=(New-Object System.Xml.XmlNodeReader $XAMLMain)
$windowMain=[Windows.Markup.XamlReader]::Load( $reader )

$windowMain.ShowDialog() | out-null

Run the code, and you should be presented with a form like this:

Result

Can PowerShell replace your old batch installation scripts?

 •  Filed under powershell, batch, automation, scripting, windows

PowerShell can be a very useful scripting language for specific tasks. Back in the old days batch scripts were the way to go for software deployment automation, often shipped with other executables to extend functionality. PowerShell adds tons of available features in one package by Microsoft, which was not possible with just a single bash script. My goal was to create a script that automates the deployment of Git for Windows, with features like architecture detection and more.

The plan

A great project always starts with a great plan, so here is a feature list that I have written and used for the development process of the script:

  • 32/64-bit detection;
  • Install path in the current user's PATH-variable;
  • Download the latest version of Git for Windows and VLC;
  • Remove any left over files automatically

Fetching installation files at runtime

Another great thing about PowerShell is that it perfectly integrates with the .NET framework. For example, you can create new .NET objects with the New-Object function. Here's a working example to download a file from the internet to your computer:

<# Simple download script demonstration #>

$SystemDrive = $env:SystemDrive
$downloadFile = "http://get.videolan.org/vlc/2.2.3/win32/vlc-2.2.3-win32.exe";
$destinationFile = "$SystemDrive\Users\Bart\Desktop\vlc-x86.exe";

(New-Object System.Net.WebClient).DownloadFile($downloadFile, $destinationFile);

There are also different ways of downloading files in PowerShell, but this is the fastest way of getting the job done. The minimal PowerShell version required to run this script is version 2. An alternative way of downloading files, called Invoke-WebRequest was introduced in PowerShell version 3 and that explains why this command didn't work on a Windows 7 install.

Architecture detection

Most software vendors supply their applications as a 32-bit and 64-bit version, and since PowerShell is able to work with WMI, you can just obtain the needed information from the Win32_OperatingSystem WMI class. Here's a code snippet to check the OS architecture:

<# Simple script to detect OS architecture #>

$OSArch = (Get-WmiObject Win32_OperatingSystem).OSArchitecture
If ($OSArch -eq "64-bit") {  
    Write-Host "OS is 64-bit!";
} ElseIf ($OSArch -eq "32-bit") {
    Write-Host "OS is 32-bit!";
}

You can replace the code located within the If statements with your own code.

Running software installers

Powershell can start processes for you with the needed arguments, wait for the process to complete and then it can delete the installer file for you. The PowerShell functions needed for this are Start-Process and Remove-Item. Here's an example:

<# Simple script to start the installation + cleanup afterwards #>

$SystemDrive = $env:SystemDrive
Start-Process "$SystemDrive\Users\Bart\Desktop\vlc-x86" -ArgumentList "/L=1033 /S" -Wait  
Remove-Item "$SystemDrive\Users\Bart\Desktop\vlc-x86.exe"  

Append directory to PATH variable

Appending the installation directory requires a little bit more programming work and knowledge. Here is an example script for you that appends the VLC installation path to your current user's PATH-variable:

<# Simple script to append the current user's path variable #>

$SystemDrive = $env:SystemDrive
$VLCPath = "C:\Program Files";

Try {  
    $Path = (Get-ItemProperty -Path 'HKCU:\Environment' -Name PATH -ErrorAction Stop).PATH
    If ($Path -ne "") {
        $NewPath = "$Path;$SystemDrive\Program Files\VideoLAN\VLC";
    } Else {
        $NewPath = "$SystemDrive\Program Files\VideoLAN\VLC";
    }
    Set-ItemProperty -Path 'HKCU:\Environment' -Name PATH -Value $NewPath
} Catch {
    If ($_.Exception.Message -like "Property PATH does not exist at path HKEY_CURRENT_USER\Environment.*") {
        New-ItemProperty -Path 'HKCU:\Environment' -Name PATH
        $NewPath = "$SystemDrive\Program Files\VideoLAN\VLC";
        Set-ItemProperty -Path 'HKCU:\Environment' -Name PATH -Value $NewPath
    } Else {
        Write-Host "Unknown error.";
    }
}

As you can see, this script works with the registry. At first, the script tries to obtain information from the HKEY_CURRENT_USER\Environment\PATH item which does not exist by default, which results in an error catched by the Catch handler that creates the needed registry entry for you automatically.

The full script

Here is the full script for installing VLC Player:

<# Install-VLC.ps1 - An automated installation script for the VLC Player #>  
<# Made by Bart Simons - https://bartsimons.me #>  
$SystemDrive = $env:SystemDrive
$User = [Environment]::UserName
$destinationFile = "$SystemDrive\Users\$User\Desktop\vlc.exe";

$OSArch = (Get-WmiObject Win32_OperatingSystem).OSArchitecture
If ($OSArch -eq "64-bit") {  
    $downloadFile = "http://get.videolan.org/vlc/2.2.3/win64/vlc-2.2.3-win64.exe";

} ElseIf ($OSArch -eq "32-bit") {
    $downloadFile = "http://get.videolan.org/vlc/2.2.3/win32/vlc-2.2.3-win32.exe"
}

(New-Object System.Net.WebClient).DownloadFile($downloadFile, $destinationFile);

Start-Process "$SystemDrive\Users\$User\Desktop\vlc.exe" -ArgumentList "/L=1033 /S" -Wait  
Remove-Item "$SystemDrive\Users\$User\Desktop\vlc.exe"  


Here is the full script for deploying Git for Windows:

<# Deploy-Git.ps1 - An automated deployment script for Git #>  
<# Made by Bart Simons - https://bartsimons.me #>  
$SystemDrive = $env:SystemDrive
$User = [Environment]::UserName
$destinationFile = "$SystemDrive\Users\$User\Desktop\git.exe";

$OSArch = (Get-WmiObject Win32_OperatingSystem).OSArchitecture
If ($OSArch -eq "64-bit") {  
    $downloadFile = "https://github.com/git-for-windows/git/releases/download/v2.8.3.windows.1/PortableGit-2.8.3-64-bit.7z.exe";

} ElseIf ($OSArch -eq "32-bit") {
    $downloadFile = "https://github.com/git-for-windows/git/releases/download/v2.8.3.windows.1/PortableGit-2.8.3-32-bit.7z.exe"
}

(New-Object System.Net.WebClient).DownloadFile($downloadFile, $destinationFile);

Start-Process "$SystemDrive\Users\$User\Desktop\git.exe" -ArgumentList "-y -gm2 -InstallPath=`"$User`"" -Wait  
Remove-Item "$SystemDrive\Users\$User\Desktop\git.exe"  

Try {  
    $Path = (Get-ItemProperty -Path 'HKCU:\Environment' -Name PATH -ErrorAction Stop).PATH
    If ($Path -ne "") {
        $NewPath = "$Path;$SystemDrive\Users\$User\git\bin";
    } Else {
        $NewPath = "$SystemDrive\Users\$User\git\bin";
    }
    Set-ItemProperty -Path 'HKCU:\Environment' -Name PATH -Value $NewPath
} Catch {
    If ($_.Exception.Message -like "Property PATH does not exist at path HKEY_CURRENT_USER\Environment.*") {
        New-ItemProperty -Path 'HKCU:\Environment' -Name PATH
        $NewPath = "$SystemDrive\Users\$User\git\bin";
        Set-ItemProperty -Path 'HKCU:\Environment' -Name PATH -Value $NewPath
    }
}