VSCode Workflow Automation | PSADT Snippet File Question

I have been using PSADT for the last 5 years and love it. But I am just at a point where I can take the time to automate some of my workflows. I have used VSCode primarily as a text editor, but I want to start leveraging its other features like Source Control and Tasks more.

I just watched this video From zero to hero VSCode, PSADT and GIT with Chris Gerke - and I was inspired to try to set something like it up.

I am unable to find a comprehensive or example VSCode Snippet File, specifically for PSADT functions and its common code blocks. I feel like I should be able to find one publically available. But the last few days of searching the interwebs isn’t finding anything PSADT specific.

Does anyone have a Snippet file they would be willing to share with me or the community at large? If not, I might take the time to work on one and share it out. But I would hate to recreate something that already exists out there. Especially one that would be better than what I could create on my own.

If anyone has comments, recommendations, resources, or warnings regarding the video I linked to above. I would love to hear about your VSCode/PSADT workflow automation setups, for software packaging, testing, and deployment.

My environment is primarily SCCM/MECM but we may bring InTune into play sometime this year… maybe.

1 Like

Sounds like a really good question to be asking, I would definitely be interested in seeing what others respond with.

I have been working towards automating as much of our application deployments as we can, so using Azure DevOps, pipelines, managed identities are all in the plan to make this process more hands-off - freeing up time for other projects.

Without wanting to rudely divert / hijack your question, I’d like to point others towards some tools to make App deployment automation a little easier…
There are some great Powershell modules out there which massively help with automating software deployments.
The 1st module I’d highly recommend to any deployment engineer is Aaron Parker’s superb Evergreen module - An absolute timesaver.
For those apps that don’t exist in EverGreen (usually because the app vendors do not supply a reliable / programatically repeatable download source), a webpage scrape is usually required - so in many cases you can additionally consider using @DanGough’s excellent NeverGreen module.

As we use Intune for our deployments, most of my focus has been on using Intune related modules to simplify package building and deployments.
I have recently been working on using the IntuneWin32App PowerShell module written by Nickolaj Andersen of MSEndpointMgr to further automate our app deployments (mainly as a personal knowledge learning experience) before expanding on this knowledge by considering the use of their Intune App Factory - mainly because it solves many of the challenges around app deployment. As part of it’s workflow it can use Evergreen, Windows Package Manager (WinGet) or an Azure Storage Account to supply updated app source files and can then use PSADT to wrap the apps of your choice, before making the app available to your chosen deployment groups. - Great solution
Only this week I have discovered something new to the mix, so for WinGet sourced apps we might consider using the simpler WinTunerApp.

Not that my point necessarily answers your question, but I hope it may help others towards their application deployment automation journey.

2 Likes

I have also been busy with Nickolaj Andersen’s IntuneWin32App PowerShell module.

I also took a part of Intune App Factory, a part of another one of his project that he abandoned and created a tool to scrape PSADT’s Deploy-Application.ps1 to create an Intune Win32App. Afterwards I just need to assign it to a group.

It currently only works using my private PSADT fork.
It also doesn’t tolerate missing jpg/png for the app’s logo, etc.

1 Like

Adrian… Those are some great resources. I will definitely look into those. that NeverGreen module could potentially plug some holes for me.

1 Like

it is possible to use hyper-v virtual machine, instead of sandbox?

So far, my workflow, is using sandbox to test the code and make sure it just runs. Then once that is done. Import it into Config Mgr. as an application and push it to Hyper-V with my org’s replicated desktop image.

This speeds up my process so far.
For apps that require more complex settings changes and dependencies. I haven’t crossed that bridge yet. This methodology is still new to me, and I haven’t had a reason to repackage my complex installs I have already created.

I would assume you could automate a hyper-v test in a similar way. I just haven’t had time to think about it much.

maybe… You edit the Bootstrap.ps1 file in the .vscode folder. Or create a new task for Hyper-V. It automates taking a snapshot of the vm (or make that a 3rd task in vscode).

It then maps a network drive to the vm and copies the test files. The script launches rdp or the vm console for that vm and logs in. Once logged in, it launches the test installer, waits for you to verify the install after script runs… You check logs, install, & settings. Then tell the script to run the uninstall. Verify the uninstall and exit rdp/console and the script reverts the snapshot.

I might try this in the future but I don’t have time at the moment. It would be good to have pre-deployed dependencies in my test VM, since I separate out dependencies from my packages.

Here are a few links I found because of your question. They might be helpful if you decide to pursue your idea.

Microsoft provides a Hyper-V powershell module
https://learn.microsoft.com/en-us/powershell/module/hyper-v/?view=windowsserver2022-ps

This link has a method of opening the console using vmconnect.exe
https://community.spiceworks.com/t/how-do-i-connect-to-hyper-v-virtual-machine-with-powershell/832529/5

If I don’t get to it first. I would be interested in what you come up with. Please post back with your results if you do.

1 Like

The video I linked to above seems to be using IntuneWin32App in his UploadToIntune.ps1 script/“vscode task”. Once we start using intune where I work. I will have to look into that feature, and possibly the Intune App Factory you linked to.

I did come up, with somethin (btw i’m very far from PowerShell expert :smiley: )

I already had a create virtual machine, with autologin enabled, so i copy the files that i want to the VM (PSADT + logoncommand)
Set up registry key on the “RUN”, to run the script at user login (that is automatically with autologin), and disable UAC.

Next step, it’s the part to automate vm creation/configuring.

function Connect-VM {
  [CmdletBinding(DefaultParameterSetName = 'name')]
  param(
    [Parameter(ParameterSetName = 'name')]
    [Alias('cn')]
    [System.String[]]$ComputerName = $env:COMPUTERNAME,
    [Parameter(Position = 0,
      Mandatory, ValueFromPipelineByPropertyName,
      ValueFromPipeline, ParameterSetName = 'name')]
    [Alias('VMName')]
    [System.String]$Name,
    [Parameter(Position = 0,
      Mandatory, ValueFromPipelineByPropertyName,
      ValueFromPipeline, ParameterSetName = 'id')]
    [Alias('VMId', 'Guid')]
    [System.Guid]$Id,
    [Parameter(Position = 0, Mandatory,
      ValueFromPipeline, ParameterSetName = 'inputObject')]
    [Microsoft.HyperV.PowerShell.VirtualMachine]$InputObject,
    [switch]$StartVM
  )
  begin {
    Write-Verbose "Initializing InstanceCount, InstanceCount = 0"
    $InstanceCount = 0
  }
  process {
    try {
      foreach ($computer in $ComputerName) {
        Write-Verbose "ParameterSetName is '$($PSCmdlet.ParameterSetName)'"
        if ($PSCmdlet.ParameterSetName -eq 'name') {
          # Get the VM by Id if Name can convert to a guid
          if ($Name -as [guid]) {
            Write-Verbose "Incoming value can cast to guid"
            $vm = Get-VM -Id $Name -ErrorAction SilentlyContinue
          }
          else {
            $vm = Get-VM -Name $Name -ErrorAction SilentlyContinue
          }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'id') {
          $vm = Get-VM -Id $Id -ErrorAction SilentlyContinue
        }
        else {
          $vm = $InputObject
        }
        if ($vm) {
          Write-Verbose "Executing 'vmconnect.exe $computer $($vm.Name) -G $($vm.Id) -C $InstanceCount'"
          vmconnect.exe $computer $vm.Name -G $vm.Id -C $InstanceCount
        }
        else {
          Write-Verbose "Cannot find vm: '$Name'"
        }
        if ($StartVM -and $vm) {
          if ($vm.State -eq 'off') {
            Write-Verbose "StartVM was specified and VM state is 'off'. Starting VM '$($vm.Name)'"
            Start-VM -VM $vm
          }
          else {
            Write-Verbose "Starting VM '$($vm.Name)'. Skipping, VM is not not in 'off' state."
          }
        }
        $InstanceCount += 1
        Write-Verbose "InstanceCount = $InstanceCount"
      }
    }
    catch {
      Write-Error $_
    }
  }
}
function Copy-FolderToVM {
  param (
    [string]$vmName,
    [string]$sourcePath,
    [string]$destPath,
    [string]$username,
    [string]$password,
    [switch]$Force
  )
  # Convert to SecureString
  $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
  $credential = New-Object System.Management.Automation.PSCredential ($username, $securePassword)
  # Check if the source path exists
  if (-Not (Test-Path -Path $sourcePath -PathType Container)) {
    Write-Error "Source path does not exist."
    return
  }
  # Creating or ensuring the destination path in the VM exists
  Invoke-Command -VMName $vmName -Credential $credential -ScriptBlock {
    param($destPath, $Force)
    if (-Not (Test-Path -Path $destPath)) {
      New-Item -Path $destPath -ItemType Directory
    }
    elseif ($Force) {
      Remove-Item -Path $destPath -Recurse -Force
      New-Item -Path $destPath -ItemType Directory
    }
  } -ArgumentList $destPath, $Force
  # Recursively copy files
  $files = Get-ChildItem -Path $sourcePath -Recurse -File
  foreach ($file in $files) {
    $relativePath = $file.FullName.Substring($sourcePath.Length).TrimStart('\')
    $destinationFilePath = Join-Path -Path $destPath -ChildPath $relativePath
    # Check if the file exists and handle based on $Force parameter
    $fileExists = Invoke-Command -VMName $vmName -Credential $credential -ScriptBlock {
      param($destinationFilePath)
      Test-Path -Path $destinationFilePath
    } -ArgumentList $destinationFilePath
    if ($fileExists -and -not $Force) {
      Write-Output "File `$destinationFilePath` exists. Use -Force to overwrite."
    }
    else {
      # Copy the file using Copy-VMFile
      Copy-VMFile -Name $vmName -SourcePath $file.FullName -DestinationPath $destinationFilePath -CreateFullPath -FileSource Host -Force
    }
  }
}
# Set Variables
$vmName = "W11"
$username = "user"
$password = "Password"
# Convert to SecureString
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($username, $securePassword)
$localFolderPath = "C:\Git\PSADT\.vscode\logonCommand_HyperV.ps1"
$localFolderPath_bin = "C:\Git\PSADT\Toolkit\"
$vmFolderPath = "\\$vmName\SharedFolder"
$scriptToRun = "C:\temp\logonCommand_HyperV.ps1"
# Start VM
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
if ($vm -eq $null) {
  Write-Host "VM not found."
  return
}
if ($vm.State -ne 'Running') {
  Write-Host "Starting VM: $vmName"
  Start-VM -Name $vmName 
  Start-Sleep -Seconds 60 # Ensure the VM is fully up and running
}
#Copy PSADT file's to VM
Copy-FolderToVM -vmName $vmName -sourcePath $localFolderPath_bin -destPath "C:\Temp\" -username $username -password $password -force
#Copy logonCommand_HyperV.ps1 to VM
$paramHash_PSSCRIPT = @{
  Name            = $vmName
  SourcePath      = 'C:\Git\PSADT\.vscode\logonCommand_HyperV.ps1'
  DestinationPath = 'C:\temp'
  CreateFullPath  = $True
  FileSource      = 'Host'
  Force           = $True
  Verbose         = $True
}
Copy-VMFile @paramHash_PSSCRIPT 
# Connect to VM
Connect-VM -VMName W11 
Start-Sleep -Seconds 60 # Ensure the VM is fully up and running
$scriptBlock = {
  $path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
  $name = "PSADT"
  $value = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy bypass -file C:\temp\logonCommand_HyperV.ps1'
  Set-ItemProperty -Path $path -Name $name -Value $value
}
# Create registry "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" value 
Invoke-Command -VMName $vmName -Credential $credential -ScriptBlock $scriptBlock 
Start-Sleep -Seconds 5
# Disable UAC
Invoke-Command -VMName $vmName -Credential $credential -ScriptBlock { Set-ItemProperty -Path REGISTRY::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System -Name ConsentPromptBehaviorAdmin -Value 0 } 
Start-Sleep -Seconds 5
#Restart VM, to  run the script at the login startup
Restart-VM -Name $vmName -Force