devices.ps1

#Requires -Version 5.0

function Test-ObjectProperty {
  param (
    [AllowNull()]
    [object]
    $InputObject,

    [Parameter(Mandatory = $true)]
    [string]
    $Name
  )

  if ($null -eq $InputObject) { return $false }
  return ($InputObject.PSObject.Properties.Name -contains $Name)
}

function Get-ObjectPropertyValue {
  param (
    [AllowNull()]
    [object]
    $InputObject,

    [Parameter(Mandatory = $true)]
    [string]
    $Name,

    [AllowNull()]
    [object]
    $DefaultValue = $null
  )

  if (-not (Test-ObjectProperty -InputObject $InputObject -Name $Name)) {
    return $DefaultValue
  }

  $_value = $InputObject.$Name
  if ($null -eq $_value) {
    return $DefaultValue
  }

  return $_value
}

function ConvertTo-PrintDevice {
  param (
    [Parameter(Mandatory = $true)]
    [object]
    $Printer,

    [AllowNull()]
    [bool]
    $Default,

    [string]
    $Source = 'PrintManagement'
  )

  $_name = [string](Get-ObjectPropertyValue -InputObject $Printer -Name 'Name')
  $_deviceId = Get-ObjectPropertyValue -InputObject $Printer -Name 'DeviceID' -DefaultValue $_name

  if (-not $PSBoundParameters.ContainsKey('Default') -or $null -eq $Default) {
    $Default = [bool](Get-ObjectPropertyValue -InputObject $Printer -Name 'Default' -DefaultValue $false)
  }

  [PSCustomObject]@{
    Name = $_name
    DeviceId = $_deviceId
    DriverName = Get-ObjectPropertyValue -InputObject $Printer -Name 'DriverName'
    PortName = Get-ObjectPropertyValue -InputObject $Printer -Name 'PortName'
    Type = Get-ObjectPropertyValue -InputObject $Printer -Name 'Type'
    Shared = [bool](Get-ObjectPropertyValue -InputObject $Printer -Name 'Shared' -DefaultValue $false)
    Published = [bool](Get-ObjectPropertyValue -InputObject $Printer -Name 'Published' -DefaultValue $false)
    Network = [bool](Get-ObjectPropertyValue -InputObject $Printer -Name 'Network' -DefaultValue $false)
    Default = [bool]$Default
    Source = $Source
  }
}

function Get-PrintDeviceDefaultMap {
  [CmdletBinding()]
  param ()

  $_defaultByName = @{}

  try {
    $_cimPrinters = @(Get-CimInstance -ClassName Win32_Printer -ErrorAction Stop)
    foreach ($_printer in $_cimPrinters) {
      $_name = [string](Get-ObjectPropertyValue -InputObject $_printer -Name 'Name')
      if ([string]::IsNullOrWhiteSpace($_name)) { continue }
      $_defaultByName[$_name] = [bool](Get-ObjectPropertyValue -InputObject $_printer -Name 'Default' -DefaultValue $false)
    }
  }
  catch {
    Write-Verbose "Could not read Win32_Printer default state: $($_.Exception.Message)"
  }

  return $_defaultByName
}

function Get-PrintDevice {
  <#
    .SYNOPSIS
      Gets installed local print devices.
    .DESCRIPTION
      Uses the PrintManagement module when available and enriches results with
      default-printer state from Win32_Printer. If PrintManagement is
      unavailable or fails, falls back to Win32_Printer directly.
    .EXAMPLE
      PS> Get-PrintDevice
    .LINK
      https://github.com/adnoctem/winkit/lib/devices.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([PSCustomObject[]])]
  [CmdletBinding()]
  param ()

  $_defaultByName = Get-PrintDeviceDefaultMap
  $_getPrinterCommand = Get-Command -Name Get-Printer -ErrorAction SilentlyContinue

  if ($_getPrinterCommand) {
    try {
      $_printers = @(Get-Printer -ErrorAction Stop)
      foreach ($_printer in $_printers) {
        $_name = [string](Get-ObjectPropertyValue -InputObject $_printer -Name 'Name')
        $_isDefault = if ($_defaultByName.ContainsKey($_name)) { $_defaultByName[$_name] } else { $false }
        ConvertTo-PrintDevice -Printer $_printer -Default $_isDefault -Source 'PrintManagement'
      }

      return
    }
    catch {
      Write-Verbose "Get-Printer failed; falling back to Win32_Printer: $($_.Exception.Message)"
    }
  }

  try {
    $_cimPrinters = @(Get-CimInstance -ClassName Win32_Printer -ErrorAction Stop)
    foreach ($_printer in $_cimPrinters) {
      ConvertTo-PrintDevice -Printer $_printer -Source 'CIM'
    }
  }
  catch {
    Write-Verbose "Could not enumerate print devices: $($_.Exception.Message)"
  }
}

function Get-DefaultPrintDevice {
  <#
    .SYNOPSIS
      Gets the default local print device.
    .DESCRIPTION
      Queries Win32_Printer for the printer where Default is true and returns
      the same normalized object shape as Get-PrintDevice.
    .EXAMPLE
      PS> Get-DefaultPrintDevice
    .LINK
      https://github.com/adnoctem/winkit/lib/devices.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([PSCustomObject])]
  [CmdletBinding()]
  param ()

  try {
    $_printers = @(Get-CimInstance -ClassName Win32_Printer -ErrorAction Stop | Where-Object { $_.Default -eq $true })
    foreach ($_printer in $_printers) {
      ConvertTo-PrintDevice -Printer $_printer -Source 'CIM'
    }
  }
  catch {
    Write-Verbose "Could not read default print device: $($_.Exception.Message)"
  }
}

function Set-DefaultPrintDevice {
  <#
    .SYNOPSIS
      Sets the default local print device.
    .DESCRIPTION
      Resolves a printer by exact name through Win32_Printer and calls its
      SetDefaultPrinter CIM method. The function returns a structured operation
      result indicating whether the default printer was changed or skipped.
    .PARAMETER Name
      Exact printer name to make the default.
    .PARAMETER PassThru
      Accepted for consistency with scripts that request operation-result
      output. The result object is returned in all cases.
    .EXAMPLE
      PS> Set-DefaultPrintDevice -Name 'Brother MFC-L2860DWE LAN'
    .LINK
      https://github.com/adnoctem/winkit/lib/devices.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([PSCustomObject])]
  [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
  param (
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string]
    $Name,

    [switch]
    $PassThru
  )

  [void]$PassThru
  $_printers = @(Get-CimInstance -ClassName Win32_Printer -ErrorAction Stop | Where-Object { $_.Name -eq $Name })

  if ($_printers.Count -eq 0) {
    Write-Error -Message "Print device '$Name' was not found." -ErrorAction Stop
  }

  if ($_printers.Count -gt 1) {
    Write-Error -Message "Multiple print devices named '$Name' were found. Refusing to choose one." -ErrorAction Stop
  }

  $_printer = $_printers[0]
  if ([bool](Get-ObjectPropertyValue -InputObject $_printer -Name 'Default' -DefaultValue $false)) {
    return New-OperationResult -Target $Name -Source 'Win32_Printer' -Action 'SetDefault' -Status 'Skipped' -Detail 'AlreadyDefault'
  }

  if (-not $PSCmdlet.ShouldProcess($Name, 'Set default print device')) {
    return New-OperationResult -Target $Name -Source 'Win32_Printer' -Action 'SetDefault' -Status 'Skipped' -Detail 'WhatIf'
  }

  try {
    $_result = Invoke-CimMethod -InputObject $_printer -MethodName 'SetDefaultPrinter' -ErrorAction Stop
    $_returnValue = Get-ObjectPropertyValue -InputObject $_result -Name 'ReturnValue' -DefaultValue 0

    if ([int]$_returnValue -eq 0) {
      return New-OperationResult -Target $Name -Source 'Win32_Printer' -Action 'SetDefault' -Status 'Completed' -Detail 'Default printer changed.'
    }

    return New-OperationResult -Target $Name -Source 'Win32_Printer' -Action 'SetDefault' -Status 'Failed' -Detail "SetDefaultPrinter returned $_returnValue."
  }
  catch {
    return New-OperationResult -Target $Name -Source 'Win32_Printer' -Action 'SetDefault' -Status 'Failed' -Detail $_.Exception.Message
  }
}

function Get-WiaPropertyValue {
  param (
    [AllowNull()]
    [object]
    $DeviceInfo,

    [Parameter(Mandatory = $true)]
    [string]
    $Name
  )

  if ($null -eq $DeviceInfo) { return $null }

  foreach ($_property in @($DeviceInfo.Properties)) {
    if ($_property.Name -eq $Name) {
      return $_property.Value
    }
  }

  return $null
}

function ConvertTo-ScanDevice {
  param (
    [Parameter(Mandatory = $true)]
    [object]
    $DeviceInfo
  )

  [PSCustomObject]@{
    Name = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Name'
    DeviceId = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Unique Device ID'
    Manufacturer = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Manufacturer'
    Type = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Type'
    Port = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Port'
    Server = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Server'
    DriverVersion = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'Driver Version'
    WiaVersion = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'WIA Version'
    PnpId = Get-WiaPropertyValue -DeviceInfo $DeviceInfo -Name 'PnP ID String'
    Source = 'WIA'
  }
}

function Get-ScanDevice {
  <#
    .SYNOPSIS
      Gets WIA-compatible scan devices.
    .DESCRIPTION
      Enumerates Windows Image Acquisition device metadata through
      WIA.DeviceManager. This is discovery-only; Windows does not provide a
      universal default scanner setting equivalent to the default printer.
    .EXAMPLE
      PS> Get-ScanDevice
    .LINK
      https://github.com/adnoctem/winkit/lib/devices.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([PSCustomObject[]])]
  [CmdletBinding()]
  param ()

  try {
    $_deviceManager = New-Object -ComObject WIA.DeviceManager
    foreach ($_deviceInfo in @($_deviceManager.DeviceInfos)) {
      ConvertTo-ScanDevice -DeviceInfo $_deviceInfo
    }
  }
  catch {
    Write-Verbose "Could not enumerate WIA scan devices: $($_.Exception.Message)"
  }
  finally {
    if (Get-Command -Name Remove-ComObject -ErrorAction SilentlyContinue) {
      Remove-ComObject $_deviceManager
    }
  }
}