Win32 OpenSSH Package
I've recently been using Macos at work lately in order to share admin responsibilities across the team. Still suppporting Windows, however there are a couple of tools I use to make working Windows from a Mac simpler.
PowerShell
Microsoft Open-Sourced PowerShell in 2016 and today in 2018, you can get stable installations for Macos, Linux and Windows on github which is often referred to as PSCore.
As an aside, this new version of powershell is not nativly backward compatible with compiled binary modules of the previous "Windows Powershell", however recently in development is a new module: WindowsCompatibility (currently only available on Windows Insider builds) that allows your to import "Windows Powershell" modules into PSCore.
When alpha and beta builds first became available I started testing remote sessions from Linux and Macos to Windows (As I would prefer to work from a unix system at work), but quickly found that the native "Enter-PSSession" wasn't supported from PSCore.
OpenSSH
Around the same time, Microsoft began working with the OpenBSD's OpenSSH project to bring official OpenSSH builds to Windows and the PSCore team found a way to make "Enter-PSSession" work with this.
Packaging PSCore
Packaging PSCore is very straightforward and I won't go into detail here. Suffice it to say that PSCore is released as an MSI and these are very simple to deploy using tools like Configuration Manager.
OpenSSH Package
Essentially I created a Windows Powershell script which follows the installation directions on the Win32 OpenSSH github Installation page.
The Scripts
Deploying an application using scripts in Configuration Manager, usually requires 3 scripts, and this case is no exception. I have provided the all needed scripts below:
Install.ps1
[CmdLetBinding()]
Param()
#region Helper functions
function Get-Path {
[CmdLetBinding()]
Param(
[ValidateSet(
"Machine",
"User"
)]$Context = "User",
[Switch]$Raw
)
If ($Context -eq "Machine") {
$Root = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
$Root = 'HKCU:'
}
If ($Raw){
Get-ItemPropertyValue -Path "$Root\Environment" -Name Path
} Else {
Try {
(Get-ItemPropertyValue -Path "$Root\Environment" `
-Name Path -EA SilentlyContinue) -split ';'
} Catch {
Write-Warning "No user environment variables found"
}
}
}
function Add-Path {
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$True)]
[ValidateScript({
if (Test-Path -Path $_) {
$True
} else {
throw "Unable to validate path $_"
}
})]$Path,
[ValidateSet(
"Machine",
"User"
)]$Context
)
Write-Verbose "Adding $Path to environment"
if ($Context -eq 'Machine') {
If (! $Path -in (Get-Path -Context Machine)){
Write-Verbose "Adding $Path to machine context"
setx /m PATH "$(Get-Path -Context Machine -Raw);$Path"
}
} else {
Write-Verbose "Adding $Path to user context"
If (! $Path -in (Get-Path -Context User)){
Write-Verbose "Adding $Path to user context"
setx PATH "$(Get-Path -Context Use -Raw);$Path"
}
}
}
function New-SymbolicLink {
Param($Link,$Target)
If (-Not (Test-Path -Path $Link)){
If ((Get-Item $Target).PSIsContainer) {
cmd.exe /c mklink /D $Link $Target
} Else {
cmd.exe /c mklink $Link $Target
}
}
}
#endregion
# Extract OpenSSH
$Archive = Get-ChildItem -Filter *.zip
Expand-Archive -Path $Archive -DestinationPath $env:ProgramFiles
Rename-Item -Path $Env:ProgramFiles\OpenSSH-Win64 -NewName OpenSSH
#Add InstallDir to Path
Add-Path -Path $Env:ProgramFiles\OpenSSH -Context Machine -Verbose
# Configure OpenSSH
& $Env:ProgramFiles\OpenSSH\install-sshd.ps1
# Start sshd service
Start-Service -Name sshd
# Set service startup
Set-Service sshd -StartupType Automatic
Set-Service ssh-agent -StartupType Automatic
# Setup pwsh link to work around
# https://github.com/PowerShell/Win32-OpenSSH/issues/784
# Find PSCore Install and Make symbolic link
$PSCoreDir = Get-ChildItem -Path $env:ProgramFiles\PowerShell `
-Directory | Select-Object -Last 1
New-SymbolicLink -Link $env:SystemDrive\pwsh -Target $PSCoreDir.FullName
# Enable Password Authentication and set pwsh as default shell
$NewConfig = Get-Content -Path $Env:ProgramData\ssh\sshd_config |
ForEach-Object {
Switch ($_) {
{$_ -match '^#PasswordAuthentication\syes'} {$_.replace('#','')}
{$_ -match '^#PubkeyAuthentication\syes'} {$_.replace('#','')}
{$_ -match '^Subsystem\s+sftp\s+'} {
'Subsystem powershell c:\pwsh\pwsh.exe -sshs -NoLogo -NoProfile'
}
Default {$_}
}
}
# Update sshd config
Set-Content -Path $Env:ProgramData\ssh\sshd_config -Value $NewConfig `
-Force
# Restart sshd
Restart-Service sshd
Uninstall.ps1
[CmdLetBinding()]
Param()
#region Helper functions
function Remove-SymbolicLink {
Param($Link,$Target)
If (Test-Path -Path $Link){
If ((Get-Item $Target).PSIsContainer) {
cmd.exe /c rmdir $Link
} Else {
cmd.exe /c del $Link
}
}
}
function Get-Path {
[CmdLetBinding()]
Param(
[ValidateSet(
"Machine",
"User"
)]$Context = "User",
[Switch]$Raw
)
If ($Context -eq "Machine") {
$Root = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
$Root = 'HKCU:'
}
If ($Raw){
Get-ItemPropertyValue -Path "$Root\Environment" -Name Path
} Else {
Try {
(Get-ItemPropertyValue -Path "$Root\Environment" `
-Name Path -EA SilentlyContinue) -split ';'
} Catch {
Write-Warning "No user environment variables found"
}
}
}
function Remove-Path {
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$True)]
$Path,
[ValidateSet(
"Machine",
"User"
)]$Context
)
Write-Verbose "Removing $Path from environment"
if ($Context -eq 'Machine') {
If ($Path -in (Get-Path -Context Machine)){
Write-Verbose "Removing $Path from machine context"
$NewPath = ""
Get-Path -Context Machine | Where-Object {
$psItem -ne $Path -and
$psItem -ne ""
} ForEach-Object {
$NewPath += "$psItem;"
}
setx /m PATH "$NewPath"
}
} else {
Write-Verbose "Removing $Path from user context"
If ($Path -in (Get-Path -Context User)){
Write-Verbose "Removing $Path from user context"
$NewPath = ""
Get-Path -Context User | Where-Object {
$psItem -ne $Path -and
$psItem -ne ""
} ForEach-Object {
$NewPath += "$psItem;"
}
setx PATH "$NewPath"
}
}
}
#endregion
& $Env:ProgramFiles\OpenSSH\uninstall-sshd.ps1
# Extract OpenSSH
Remove-Item -Path $env:ProgramFiles\OpenSSH -Recurse -Force
Remove-Path -Path $env:ProgramFiles\OpenSSH -Context Machine -Verbose
# Find PSCore Install and remove symbolic link
$PSCoreDir = Get-ChildItem -Path $env:ProgramFiles\PowerShell -Directory | Select-Object -Last 1
Remove-SymbolicLink -Link $env:SystemDrive\pwsh -Target $PSCoreDir.FullName
# Remove old config
Remove-Item -Path $env:ProgramData\ssh -Recurse -Force
Detect.ps1
$AssumeInstalled = $True
If (-Not (Test-Path $Env:ProgramFiles\OpenSSH)) {
$AssumeInstalled = $False
}
If (-Not (Test-Path $Env:SystemDrive\pwsh)) {
$AssumeInstalled = $False
}
If (-Not (Get-Service sshd -ErrorAction SilentlyContinue)) {
$AssumeInstalled = $False
}
If ($AssumeInstalled) {
Write-Output "True"
}
Using OpenSSH with Powershell
Now that I have used these scripts to deploy OpenSSH and PSCore, I can PSRemote to a PC using my Mac.
The old way to use "Enter-PSSession" was by specifying the ComputerName parameter like so:
PS\> Enter-PSSession -ComputerName Blah
However, when using OpenSSH with a PS Session you do the following:
PS\> Enter-PSSession -HostName Blah -UserName MrBlah
You could also setup a session in a variable and resue it multiple times in a session:
PS\> $s = New-PSSesssion -HostName Blah -UserName MrBlah
PS\> Enter-PSSession -Session $s
[Blah] PS\>
I hope you have found this usefull.
Tags: powershell, pscore, openssh, windows, macos
New-AppVSequencerVM
In Windows 10 1703 microsoft introduced some new Powershell Commandlets for APPV. New-AppVSequencerVM and New-BatchAppVSequencerPackages and Connect-AppVSequencerVM
In this post, I will explorer using these commandlets to package some software.
First we have to download some tools. In this blog post, I've used Convert-WindowsImage from the Technet Gallery to convert a Windows 10 ISO to a VHDX. You can get it from here: Download Convert-WindowsImage.ps1: TechNet Gallery
Download an Enterprise Creators Update ISO (I got mine from my MSDN account)
Download Windows ADK 1703: ADK Download
First here is the commands I ran to convert my 1703 ISO to a vhdx:
The 'Convert-WindowsImage.ps1' script only contains functions like a module, so dot source it to load the functions:
C:\>cd SeqTest
C:\SeqTest\> . .\Convert-WindowsImage.ps1
C:\SeqTest\> Convert-WindowsImage `
-SourcePath Win10-1703.iso `
-Edition Enterprise -WorkingDirectory .\
Tags: appv, powershell, 1703, adk
Yet another Dell Warranty Configuration Manager Hardware Inventory Extension Post
You've read it before, post upon post upon post. You've trawled through the comments searching for answers, analysing html data, scraping websites, fudging data and whatnot. All for what?
The Holy Grail of Inventory extensions
Configuration Manager has its roots in client management, not Asset management. Microsoft have added Asset Intelligence capabilities to Configuration Manager and improved its usefulness as an Asset Management tool, but it still has a long way to go. For example, other tools specifically designed for Asset Management would be able to perform this task by simply importing a spreadsheet.
A quick overview of the steps for this:
Gather the data
Scrape a website or batch process with Lenovo
Make the data available
Make it so machines can query a service to find their warranty info from your network without them having to scrape a site for you
Deploy a script for workstations to ingest data into their registry
Each client device will record its warranty end date in registry
Extend the Inventory
Configuration Manager will run a mofcomp of the site configurationmanager.mof file. This copies the registry data into WMI
Run a hardware inventory
This will gather the data and import it into the Configuration Manager SQL database
Inject the database with remaining data to speed up the process.
Many devices won't run the script or connect to our system quick enough for us to have useful data. I'm going to do the unthinkable and SQL INSERT my data into the SQL DB
Create a Collection based on devices due to be replaced in Q4 2016
Gather the data
Dell
Every man and his dog has written a web scraper for Dells site. I did this years ago when a higher up wanted to fill in the blanks. People like me are why Dell now put a capcha block in front of the warranty info. It's annoying but it's not a show-stoppa.
Shipping Date
Dell still shows the shipping date plain and simple on their site, so it's not too hard to go from Shipping Date + Warranty period to arrive at Warranty End Date.
For us this is easy, we buy all our Desktops with 4 years warranty and all our laptops with 3 years warranty.
Here is the code:
function Get-DellWarrantyStatus {
<#
.SYNOPSIS
Checks to see the input computername matches what a Dell/Lenovo
service tag should be. If so, checks a looks up cache csv file for a
matching record and returns warranty end date. If the record is
not found in the CSV, the machine Manufacturer is retrieved from
ConfigMgr and then the data is retrieved from the Dell or Lenovo
website displayed and stored locally.
.DESCRIPTION
Usefull tool for looking up Warranty end date of Dell and Lenovo hardware.
.PARAMETER ComputerName
Specifies a ComputerName record to query. Must correspond with a
dell servicetag or Lenovo Serial number.
.EXAMPLE
C:\PS>Get-DellWarrantyStatus -ComputerName 6wmpsn1
ComputerName WarrantyEndDate Source
------------ --------------- ------
6wmpsn1 19/11/2013 12:00:00 AM Cache
.EXAMPLE
C:\PS>Get-DellWarrantyStatus (Get-Content D:\temp\computers.txt)
ComputerName WarrantyEndDate Source
------------ --------------- ------
2WDX92S 31/08/2015 12:00:00 AM Remote
JTDX92S 31/08/2015 12:00:00 AM Remote
5WDX92S 31/08/2015 12:00:00 AM Remote
7LCX92S 31/08/2015 12:00:00 AM Remote
.NOTES
Author: Jesse Harris
For: University of Sunshine Coast
Date Created: 26 October 2012
ChangeLog:
1.0 - First Release
2.0 - Added Lenovo as a supported Manufacturer
#>
[CmdletBinding()]
Param(
[Parameter(
ValueFromPipeline=$True,
ValueFromPipelinebyPropertyName=$True)]
[string[]]$ComputerName="$env:computername"
)
Begin {
$CacheFile = $env:AppData+'\USC\DellWarranty.csv'
If (Test-Path $CacheFile) {
$Cache = Import-CSV -Path $CacheFile
} Else {
New-Item -ItemType File -Path $CacheFile -Force
}
}
PROCESS {
function Get-DWWorker {
Param($Name)
If (($Cache) -and ($Name -in $Cache.ComputerName)) {
$Cache | Where-Object {$_.ComputerName -eq $Name}
} Else {
#Dell Code
$Model = (Get-CfgClientInventory -ComputerName $Name -Properties Model).Model
Write-Verbose "Model $Model"
$WarrantyURL =
"http://www.dell.com/support/home/us/en/19/product-support/servicetag/" + `
"$Name/configuration"
try {
$WarrantyPage = Invoke-WebRequest -Uri $WarrantyURL
} catch [System.SystemException] {
Write-Error "Unable to communicate with Warranty URL"
return
}
$WPageText = $WarrantyPage.AllElements | ?{$_.id -eq "subSectionA"} `
| select -ExpandProperty outertext
[regex]$regex = '\d{1,2}/\d{1,2}/\d{4}'
$DateString = ([string]($regex.Matches($WPageText)))
Write-Verbose "Datestring = $DateString"
$DateStringSplit = $DateString -Split "/"
#Write-Verbose $DateString -Verbose
try {
$Day = [Decimal]$DateStringSplit[0].ToString("00")
$Month = [Decimal]$DateStringSplit[1].ToString("00")
$Yeat = [Decimal]$DateStringSplit[2]
$ConvertedDate = "$Day/$Month/$Year"
$DateObject =
[DateTime]::ParseExact($ConvertedDate,"d",[cultureinfo]::InvariantCulture)
} catch [System.SystemException] {
Write-Error "Cannot convert DateTime, Page structure may have changes. Contact Jesse"
BREAK
}
Write-Verbose "Adjusting Date for Model ""$Model"""
Switch ([string]$Model) {
{$_ -match "Latitude"} {
#Add 3 years to ship date
Write-Verbose "Adding 3 years to ship date"
$Date = (Get-Date $DateObject).AddYears(3)
}
{$_ -match "OptiPlex"} {
#Add 4 years to ship date
Write-Verbose "Adding 4 years to ship date"
$Date = (Get-Date $DateObject).AddYears(4)
}
{$_ -match "Precision M"} {
#Add 3 years
Write-Verbose "Adding 3 years to ship date"
$Date = (Get-Date $DateObject).AddYears(3)
}
default {
#Add 4 years default
Write-Verbose "Unable to detect model, adding 4 years to ship date"
$Date = (Get-Date $DateObject).AddYears(4)
}
}
}
$WarrantyEOL = New-Object -TypeName PSCustomObject
$WarrantyEOL | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Name
$WarrantyEOL | Add-Member -MemberType NoteProperty -Name WarrantyEndDate -Value (Get-Date $Date)
#Update cache in memory
$Cache = $Cache + $WarrantyEOL
#Update cache on disk
$WarrantyEOL | Export-CSV -Append -Path $CacheFile -Force
#Show result
$WarrantyEOL | Add-Member -MemberType NoteProperty -Name Source -Value "Remote" -PassThru
}
}
If ($PSBoundParameters.ContainsKey('ComputerName')) {
Foreach ($Computer in $ComputerName) {
Get-DWWorker -Name $Computer
}
} Else {
Get-DWWorker -Name $ComputerName
}
}
Tags: powershell, dell