Using Nuget package feed from PowerShell in Azure pipeline - azure-devops

I'm trying to write a pipeline PowerShell task that checks that package version numbers in the csproj files have been incremented -- so that the push step doesn't fail because of duplicates when building new release packages. (We use allowPackageConflicts: false on the NuGetCommand#2 command: push task.)
The problem is to get Find-Package to use my Azure DevOps NuGet feed that all the packages go to.
- task: PowerShell#2
displayName: 'Check package version numbers'
inputs:
targetType: 'inline'
script: |
[bool] $bad = $false;
Get-PackageSource
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Register-PackageSource -Name TheAzureDevOpsFeed -Trusted -Force -ProviderName NuGet -Location "https://pkgs.dev.azure.com/Organisation/_packaging/TheAzureDevOpsFeed/nuget/v3/index.json"
Get-PackageSource
foreach( $csproj in Get-ChildItem **\*.csproj ) {
$m = Select-String -Path $csproj.FullName -Pattern '<VersionPrefix>([\d\.]+)</VersionPrefix>'
$pkgName = [System.IO.Path]::GetFileNameWithoutExtension($csproj.FullName)
echo $pkgName
if( $m.Matches.Count -eq 1 )
{
$csprojVersion = $m.Matches[0].Groups[1].Value
$pkg = $null
try {
$pkg = Find-Package $pkgName -Source TheAzureDevOpsFeed -ErrorAction Stop
}
catch {
$pkg = $null
}
if( $pkg -eq $null )
{
echo " No existing package."
continue
}
$current = $pkg.Versions[0].OriginalVersion
if( [System.Version]"$csprojVersion" -le [System.Version]"$current" )
{
echo " REJECTED: current version is $current but csproj has version $csprojVersion."
$bad = $true
}
else {
echo " $current -> $csprojVersion"
}
}
else {
echo " skipped"
}
}
if( $bad ) {
exit 1
}
exit 0
When the task runs the output is:
Name ProviderName IsTrusted Location
---- ------------ --------- --------
nuget.org NuGet False https://api.nuget.org/v3/index.json
PSGallery PowerShellGet False https://www.powershellgallery.com/api/v2
Register-PackageSource : Source Location
'https://pkgs.dev.azure.com/Organisation/_packaging/TheAzureDevOpsFeed/nuget/v3/index.json' is not valid.
At D:\a\_temp\62c6a3fb-93a6-4d4a-8cf5-ce5dc7d9dcbf.ps1:7 char:1
+ Register-PackageSource -Name TheAzureDevOpsFeed -Trusted -Force -Provi ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (TheAzureDevOpsFeed:String) [Register-PackageSource], Exception
+ FullyQualifiedErrorId : SourceLocationNotValid,Microsoft.PowerShell.PackageManagement.Cmdlets.RegisterPackageSou
rce
But why is it 'not valid'? It is valid -- it works fine in VisualStudio.
Things I've tried:
single quotes, double quotes, no quotes,
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12,
-Location https://pkgs.dev.azure.com/.../v2

I can reproduce your issue:
How I solve this:
$patToken = "<Your PAT here>" | ConvertTo-SecureString -AsPlainText -Force
$credsAzureDevopsServices = New-Object System.Management.Automation.PSCredential("<Your email address that related to PAT here>", $patToken)
Register-PackageSource -Name "xxx" -Location "https://pkgs.dev.azure.com/<organizaton name>/_packaging/<feed name>/nuget/v2" -ProviderName NuGet -Credential $credsAzureDevopsServices
Please refer to these official documents:
https://learn.microsoft.com/en-us/powershell/module/packagemanagement/register-packagesource?view=powershell-7.2
https://learn.microsoft.com/en-us/azure/devops/artifacts/tutorials/private-powershell-library?view=azure-devops#connecting-to-the-feed-as-a-powershell-repo
Let me know if you have more concerns.

Related

Problem with downloading language local experience file using Powershell script

I use a fantastic script to download LXP files from microsoft and install them. This script works fine with languages like: en-us, pl-pl, zh-tw, zh-cn, fr-fr, ja-jp and maybe more BUT there is a strange issue with languages like italian (it-it), spanish (es-es) and portuguese (pt-pt). The script shows an error that the Invoke-WebRequest can't connect to the server and the rest of script fails because next variables are empty. Maybe sb will have an idea how to solve it.
There is a script I am talking about with es-es language:
# the language we want as new default
$language = "es-ES"
Start-Transcript -Path "$env:TEMP\LXP-SystemContext-Installer-$language.log" | Out-Null
# https://www.microsoft.com/de-de/p/deutsch-local-experience-pack/9p6ct0slw589
$applicationId = "9NWVGWLHPB1Z" # spanish
$webpage = Invoke-WebRequest -UseBasicParsing -Uri "https://bspmts.mp.microsoft.com/v1/public/catalog/Retail/Products/$applicationId/applockerdata"
$packageFamilyName = ($webpage | ConvertFrom-JSON).packageFamilyName
$skuId = 0016
$geoId = 217 # Spain
$inputLanguageID = "0c0a:0000040a" # es-es
# custom folder for temp scripts
"...creating custom temp script folder"
$scriptFolderPath = "$env:SystemDrive\ProgramData\CustomTempScripts"
New-Item -ItemType Directory -Force -Path $scriptFolderPath
"`n"
$languageXmlPath = $(Join-Path -Path $scriptFolderPath -ChildPath "MUI.xml")
# language xml definition for intl.cpl call to switch the language 'welcome screen' and 'new user' defaults
$languageXml = #"
<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
<!-- user list -->
<gs:UserList>
<gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/>
</gs:UserList>
<!-- GeoID -->
<gs:LocationPreferences>
<gs:GeoID Value="$geoId"/>
</gs:LocationPreferences>
<!-- UI Language Preferences -->
<gs:MUILanguagePreferences>
<gs:MUILanguage Value="$language"/>
</gs:MUILanguagePreferences>
<!-- system locale -->
<gs:SystemLocale Name="$language"/>
<!-- input preferences -->
<gs:InputPreferences>
<gs:InputLanguageID Action="add" ID="$inputLanguageID" Default="true"/>
</gs:InputPreferences>
<!-- user locale -->
<gs:UserLocale>
<gs:Locale Name="$language" SetAsCurrent="true" ResetAllSettings="false"/>
</gs:UserLocale>
</gs:GlobalizationServices>
"#
$userConfigScriptPath = $(Join-Path -Path $scriptFolderPath -ChildPath "UserConfig.ps1")
# we could encode the complete script to prevent the escaping of $, but I found it easier to maintain
# to not encode. I do not have to decode/encode all the time for modifications.
$userConfigScript = #"
`$language = "$language"
Start-Transcript -Path "`$env:TEMP\LXP-UserSession-Config-`$language.log" | Out-Null
`$geoId = $geoId
"explicitly register the LXP in current user session (Add-AppxPackage -Register ...)"
`$appxLxpPath = (Get-AppxPackage | Where-Object Name -Like *LanguageExperiencePack`$language).InstallLocation
Add-AppxPackage -Register -Path "`$appxLxpPath\AppxManifest.xml" -DisableDevelopmentMode
# important for regional change like date and time...
"Set-WinUILanguageOverride = `$language"
Set-WinUILanguageOverride -Language `$language
"Set-WinUserLanguageList = `$language"
#Set-WinUserLanguageList `$language -Force
# changed handling due to new knowledge :-)
# https://oliverkieselbach.com/2021/01/13/company-portal-stuck-in-a-different-language/
`$OldList = Get-WinUserLanguageList
`$UserLanguageList = New-WinUserLanguageList -Language `$language
`$UserLanguageList += `$OldList | where { `$_.LanguageTag -ne `$language }
"Setting new user language list:"
`$UserLanguageList | select LanguageTag
""
"Set-WinUserLanguageList -LanguageList ..."
Set-WinUserLanguageList -LanguageList `$UserLanguageList -Force
"Set-WinSystemLocale = `$language"
Set-WinSystemLocale -SystemLocale `$language
"Set-Culture = `$language"
Set-Culture -CultureInfo `$language
"Set-WinHomeLocation = `$geoId"
Set-WinHomeLocation -GeoId `$geoId
Stop-Transcript -Verbose
"#
Set-WinUserLanguageList -LanguageList $language -Force
$userConfigScriptHiddenStarterPath = $(Join-Path -Path $scriptFolderPath -ChildPath "UserConfigHiddenStarter.vbs")
$userConfigScriptHiddenStarter = #"
sCmd = "powershell.exe -ex bypass -file $userConfigScriptPath"
Set oShell = CreateObject("WScript.Shell")
oShell.Run sCmd,0,true
"#
"...set reg key: BlockCleanupOfUnusedPreinstalledLangPacks = 1"
& REG add "HKLM\Software\Policies\Microsoft\Control Panel\International" /v BlockCleanupOfUnusedPreinstalledLangPacks /t REG_DWORD /d 1 /f /reg:64
"`n"
$packageName = "Microsoft.LanguageExperiencePack$language"
# check for installed Language Experience Pack (LXP), maybe already installed?
$status = $(Get-AppxPackage -AllUsers -Name $packageName).Status
# create CIM session here as we need it for Store updates as well
$session = New-CimSession
try {
if ($status -ne "Ok")
{
try {
$namespaceName = "root\cimv2\mdm\dmmap"
$omaUri = "./Vendor/MSFT/EnterpriseModernAppManagement/AppInstallation"
$newInstance = New-Object Microsoft.Management.Infrastructure.CimInstance "MDM_EnterpriseModernAppManagement_AppInstallation01_01", $namespaceName
$property = [Microsoft.Management.Infrastructure.CimProperty]::Create("ParentID", $omaUri, "string", "Key")
$newInstance.CimInstanceProperties.Add($property)
$property = [Microsoft.Management.Infrastructure.CimProperty]::Create("InstanceID", $packageFamilyName, "String", "Key")
$newInstance.CimInstanceProperties.Add($property)
$flags = 0
$paramValue = [Security.SecurityElement]::Escape($('<Application id="{0}" flags="{1}" skuid="{2}"/>' -f $applicationId, $flags, $skuId))
$params = New-Object Microsoft.Management.Infrastructure.CimMethodParametersCollection
$param = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("param", $paramValue, "String", "In")
$params.Add($param)
# we create the MDM instance and trigger the StoreInstallMethod to finally download the LXP
$instance = $session.CreateInstance($namespaceName, $newInstance)
$result = $session.InvokeMethod($namespaceName, $instance, "StoreInstallMethod", $params)
"...Language Experience Pack install process triggered via MDM/StoreInstall method"
}
catch [Exception] {
write-host $_ | out-string
$exitcode = 1
}
}
if (($result.ReturnValue.Value -eq 0) -or ($status -eq "Ok")) {
if ($status -ne "Ok")
{
"...busy wait until language pack found, max 15 min."
$counter=0
do {
Start-Sleep 10
$counter++
# check for installed Language Experience Pack (LXP)
$status = $(Get-AppxPackage -AllUsers -Name $packageName).Status
} while ($status -ne "Ok" -and $counter -ne 90) # 90x10s sleep => 900s => 15 min. max wait time!
}
# print some LXP package details for the log
Get-AppxPackage -AllUsers -Name $packageName
if ($status -eq "Ok") {
"...found Microsoft.LanguageExperiencePack$language with Status=Ok"
"...trigger install for language FOD packages"
# add Windows capabilities / FODs to avoid UAC dialog after restart
# Parameter -Online will reach out to Windows Update to get the latest correct source files
Get-WindowsCapability -Online | Where-Object Name -ilike "Language.*~~~$($language)~*" | ForEach-Object {
if ($_.State -ine "Installed") {
"Adding windows capability '$($_.Name)'..."
$_ | Add-WindowsCapability -Online | Out-Null
}
else {
"Windows capability '$($_.Name)' is already installed.`n"
}
}
"`n"
"...trigger language change for current user session via ScheduledTask = LXP-UserSession-Config-$language"
Out-File -FilePath $userConfigScriptPath -InputObject $userConfigScript -Encoding ascii
Out-File -FilePath $userConfigScriptHiddenStarterPath -InputObject $userConfigScriptHiddenStarter -Encoding ascii
# REMARK: usag of wscript as hidden starter may be blocked because of security restrictions like AppLocker, ASR, etc...
# switch to PowerShell if this represents a problem in your environment.
$taskName = "LXP-UserSession-Config-$language"
$action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument "$userConfigScriptHiddenStarterPath"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId (Get-CimInstance –ClassName Win32_ComputerSystem | Select-Object -expand UserName)
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask $taskName -InputObject $task
Start-ScheduledTask -TaskName $taskName
Start-Sleep -Seconds 30
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
# trigger 'LanguageComponentsInstaller\ReconcileLanguageResources' otherwise 'Windows Settings' need a long time to change finally
"...trigger ScheduledTask = LanguageComponentsInstaller\ReconcileLanguageResources"
Start-ScheduledTask -TaskName "\Microsoft\Windows\LanguageComponentsInstaller\ReconcileLanguageResources"
Start-Sleep 10
# change 'welcome screen' and 'new user' language defaults
"...trigger language change for welcome screen and new user defaults"
Out-File -FilePath $languageXmlPath -InputObject $languageXml -Encoding ascii
# check eventlog 'Microsoft-Windows-Internationl/Operational' for troubleshooting
& $env:SystemRoot\System32\control.exe "intl.cpl,,/f:`"$languageXmlPath`""
# trigger store updates, there might be new app versions due to the language change
"...trigger MS Store updates for app updates"
Get-CimInstance -Namespace "root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | Invoke-CimMethod -MethodName "UpdateScanMethod"
$exitcode = 0
}
}
else {
$exitcode = 1
}
"...cleanup and finish"
try {
$session.DeleteInstance($namespaceName, $instance) | Out-Null
Remove-CimSession -CimSession $session
} catch {}
Remove-Item -Path $scriptFolderPath -Force -Recurse -ErrorAction SilentlyContinue
}
catch [Exception] {
# maybe a left over to clean, but prevent aditional errors and set exitcode
try {
$session.DeleteInstance($namespaceName, $instance) | Out-Null
} catch {}
$exitcode = 1
}
if ($exitcode -eq 0) {
$installed = 1
}
else {
$installed = 0
}
# IME app install runs by default in 32-bit so we write explicitly to 64-bit registry
& REG add "HKLM\Software\MyIntuneApps" /v "SetLanguage-$language" /t REG_DWORD /d $installed /f /reg:64 | Out-Null
Stop-Transcript -Verbose
exit $exitcode
I tried to find my mistakes in ID etc also checking if the requested language is available (and it is!). I tried to run the invoke-webrequest alone and it also works.
The error msg:
PS>TerminatingError(Invoke-WebRequest): "Unable to connect to remote server"
Invoke-WebRequest : Unable to connect to remote server
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:47 char:12
+ $webpage = Invoke-WebRequest -UseBasicParsing -Uri "https://bspmts.mp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebExcep
tion
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Invoke-WebRequest : Unable to connect to remote server
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:47 char:12
+ $webpage = Invoke-WebRequest -UseBasicParsing -Uri "https://bspmts.mp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebExc
eption
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null.
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:48 char:34
+ $packageFamilyName = ($webpage | ConvertFrom-JSON).packageFamilyName
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromJs
onCommand
ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null.
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:48 char:34
+ $packageFamilyName = ($webpage | ConvertFrom-JSON).packageFamilyName
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFrom
JsonCommand

Get-AzKeyVaultSecret -VaultName..... | Name or service not known

I have a pipeline in which i calling a power-shell script which copy the azure keyvault secrets from one key-vault to another keyvault.
Here's the powershell script:
$SecretNames = "api-gateway--jwt-public-key",
"authentication-service--jwt-private-key",
"user-management--pen-password",
"user-management--stripe-secret-key"
$sourceVaultName="fdevcuskv03"
$destVaultName="fdevcuskv04"
for (($i = 0); $i -lt $SecretNames.Count; $i++)
{
$sourceSecretName = "$($SecretNames[$i])"
$destSecretName = "$($SecretNames[$i])"
$Getvalue=(Get-AzKeyVaultSecret -VaultName $sourceVaultName -Name $sourceSecretName).SecretValue
Write-Host "Copying $sourceSecretName Value To $destSecretName"
Set-AzKeyVaultSecret -VaultName $destVaultName -Name $destSecretName `
-SecretValue $Getvalue
}
When I run the pipeline, I got this error but this works fine locally.
Here's the error:
Get-AzKeyVaultSecret: /home/vsts/work/1/s/Terraform/Terraform-Scripts/main.ps1:351
Line |
351 | … $Getvalue=(Get-AzKeyVaultSecret -VaultName $sourceVaultName -Name $s …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Name or service not known
I'm bit confused, what i'm doing wrong.
Along with checking that please check also if the case maybe dns resolution issue or invalid dns cache causing the error .
For that please try to give it sleep time and repeat the step.(Also check by dns flush )
Place check azure-powershell issues(github) comment by #placidseven ang set azure keyvault by first checking if dns Is resolved.
foreach(($i = 0); $i -lt $SecretNames.Count; $i++)
{
$sourceSecretName = "$($SecretNames[$i])"
$destSecretName = $sourceSecretName
$Getvalue=(Get-AzKeyVaultSecret -VaultName $sourceVaultName -Name $sourceSecretName).SecretValue
Write-Host "Copying $sourceSecretName Value To $destSecretName"
setSecret
function setSecret{
while (!$secret) {
$DnsCheck = Resolve-DnsName $VaultURI -ErrorAction SilentlyContinue
if (!$DnsCheck) {
write-host "Resolve-DnsName taking time to resolve $vaultName. Keep trying!"
Start-Sleep -Seconds 30
Set-AzKeyVaultSecret -VaultName $destVaultName -Name $destSecretName `
-SecretValue $Getvalue -ErrorAction SilentlyContinue
}
}
$secret = Set-AzKeyVaultSecret -VaultName $destVaultName -Name $destSecretName `
-SecretValue $Getvalue -ErrorAction SilentlyContinue
setSecret
}
}
Reference: Set-AzureKeyVaultSecret does not recognize vaultName · GitHub

The term 'Install-ChocolateyPackage' is not recognized as the name of a cmdlet, function

i try to install SQLEXPRADV_x64 "manual" with Install-ChocolateyPackage on an Azure Hosted Agent (Windows-Latest). I do manual as there is no Express Advanced Version available.
Error is:
The term 'Install-ChocolateyPackage' is not recognized as the name of a cmdlet, function
And I can't find the fitting solution in the documentation. The CMDLET should be available on the Agent?
- powershell: |
$silentArgs = "/IACCEPTSQLSERVERLICENSETERMS /Q /ACTION=install /INSTANCEID=SQLEXPRESS /INSTANCENAME=SQLEXPRESS /UPDATEENABLED=FALSE"
$fileFullPath = "SQLEXPRADV_x64_ENU.exe"
$packageName = "MsSqlServer2016ExpressAdv"
$chocolateyTempDir = Join-Path (Get-Item $env:TEMP).FullName "chocolatey"
$tempDir = Join-Path $chocolateyTempDir $packageName
$extractPath = "$tempDir\SQLEXPRADV"
$setupPath = "$extractPath\setup.exe"
Write-Host "Extracting to " $extractPath
Start-Process "$fileFullPath" "/Q /x:`"$extractPath`"" -Wait
Install-ChocolateyPackage "$packageName" "EXE" "$silentArgs" "$setupPath" -validExitCodes #(0, 3010)
displayName: 'install sql express database'
grzegorz-ochlik is correct: Why is the Uninstall-ChocolateyPackage cmdlet not recognized?
I solved it without Chocolatey by just using Start-Process
- powershell: |
$silentArgs = "/IACCEPTSQLSERVERLICENSETERMS /Q /ACTION=install /INSTANCEID=SQLEXPRESS02 /INSTANCENAME=SQLEXPRESS /UPDATEENABLED=FALSE"
$fileFullPath = "SQLEXPRADV_x64_ENU.exe"
$packageName = "MsSqlServer2019ExpressAdv"
$currentlocation = Get-Location
$tempDir = Join-Path $currentlocation $packageName
$extractPath = "$tempDir\SQLEXPRADV"
$setupPath = "$extractPath\setup.exe"
Write-Host "Extracting to " $extractPath
Start-Process "$fileFullPath" "/Q /x:`"$extractPath`"" -Wait
Start-Process $setupPath -ArgumentList $silentArgs
displayName: 'install sql express database'

Issues running a Powershell script without running a VB script first

I was looking for a solution to pin a shortcut or program to the task in win 10 with PS. I found Pin program to taskbar using PS in Windows 10. The VB Script works,
If WScript.Arguments.Count < 1 Then WScript.Quit
'----------------------------------------------------------------------
Set objFSO = CreateObject("Scripting.FileSystemObject")
objFile = WScript.Arguments.Item(0)
sKey1 = "HKCU\Software\Classes\*\shell\{:}\\"
sKey2 = Replace(sKey1, "\\", "\ExplorerCommandHandler")
'----------------------------------------------------------------------
With WScript.CreateObject("WScript.Shell")
KeyValue = .RegRead("HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" & _
"\CommandStore\shell\Windows.taskbarpin\ExplorerCommandHandler")
.RegWrite sKey2, KeyValue, "REG_SZ"
With WScript.CreateObject("Shell.Application")
With .Namespace(objFSO.GetParentFolderName(objFile))
With .ParseName(objFSO.GetFileName(objFile))
.InvokeVerb("{:}")
End With
End With
End With
.Run("Reg.exe delete """ & Replace(sKey1, "\\", "") & """ /F"), 0, True
End With
'----------------------------------------------------------------------
I can invoke VB script from PS but a helpful person converted the script to PS
Param($Target)
$KeyPath1 = "HKCU:\SOFTWARE\Classes"
$KeyPath2 = "*"
$KeyPath3 = "shell"
$KeyPath4 = "{:}"
$ValueName = "ExplorerCommandHandler"
$ValueData =
(Get-ItemProperty `
("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\" + `
"CommandStore\shell\Windows.taskbarpin")
).ExplorerCommandHandler
$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
$Key3 = $Key2.CreateSubKey($KeyPath3, $true)
$Key4 = $Key3.CreateSubKey($KeyPath4, $true)
$Key4.SetValue($ValueName, $ValueData)
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
$Item.InvokeVerb("{:}")
$Key3.DeleteSubKey($KeyPath4)
if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0) {
$Key2.DeleteSubKey($KeyPath3)
}
However this PS script will not run unless the VB script has been ran at least one time. Is there a way to make the PS script work without having to run the VB script?
The error I get when trying to run the PS script without running the VB script at least once before it:
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:41 char:5
+ $Key3 = $Key2.CreateSubKey($KeyPath3, $true)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:42 char:5
+ $Key4 = $Key3.CreateSubKey($KeyPath4, $true)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:43 char:5
+ $Key4.SetValue($KeyValue, $ValueData)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:50 char:5
+ $Key3.DeleteSubKey($KeyPath4)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
I do not get an error after using the VB script once to do the task.
You should not have been impacted by this this way.
The code works as designed, but you have to call the path the exe fully.
I just converted it to a function and it is successful with no other dependencies.
Function Add-AppToTaskbar
{
[cmdletbinding()]
Param
(
[string]$Target
)
$KeyPath1 = "HKCU:\SOFTWARE\Classes"
$KeyPath2 = "*"
$KeyPath3 = "shell"
$KeyPath4 = "{:}"
$ValueName = "ExplorerCommandHandler"
$ValueData =
(Get-ItemProperty `
("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\" + `
"CommandStore\shell\Windows.taskbarpin")
).ExplorerCommandHandler
$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
$Key3 = $Key2.CreateSubKey($KeyPath3, $true)
$Key4 = $Key3.CreateSubKey($KeyPath4, $true)
$Key4.SetValue($ValueName, $ValueData)
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
$Item.InvokeVerb("{:}")
$Key3.DeleteSubKey($KeyPath4)
if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0)
{$Key2.DeleteSubKey($KeyPath3)}
}
Add-AppToTaskbar -Target 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
BTW, these pinned things live in two places on your system:
Here:
$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
Registry:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband
Both are required.
Update based on OP's comment
I just ran this locally and remotely, both are successful. See results below.
The local host I am using - WS2012R2 set as a workstation role
I don't have any W10 systems in my lab. The earlier test was on a local W10 host.
Executed in the console host, ISE and VSCode.
PS C:\Windows\system32> $env:COMPUTERNAME
LabWS01
# PS Version
PS C:\Windows\system32> $PSVersionTable
Name Value
---- -----
PSVersion 4.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.42000
BuildVersion 6.3.9600.18968
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion 2.2
# the current user profile pinned location filtered for notepad*
PS C:\Windows\system32> Get-ChildItem -Path "$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Notepad*"
# Tested path to remote share
PS C:\Windows\system32> Test-path -Path '\\Server\ShareName\Add-AppToTaskbar.ps1'
True
# Ran the script from that remote share
PS C:\Windows\system32> \\Server\ShareName\Add-AppToTaskbar.ps1 'c:\windows\notepad.exe'
or this way...
Start-process -FilePath Powershell -ArgumentList '\\Server\ShareName\Add-AppToTaskbar.ps1 -Target C:\Windows\notepad.exe'
# Review pinned item location, filtered for notepad*
PS C:\Windows\system32> Get-ChildItem -Path "$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Notepad*"
Directory: C:\Users\Labuser001\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 8/9/2018 8:48 PM 791 Notepad.lnk
Shortcut shows pinned to taskbar.
So, this sounds environmental on your side. Now you can pin apps using GPO if this issue continues.
I've modified your function so that selectively pins or unpins items to the taskbar. Previously, the problem is that the pin command was not exclusive, it would unpin the application if it was already pinned. With further detection of what was pinned in a binary registry value, it has been possible to determine that the item has already been pinned and it will not attempt to pin the item twice.
Set-AppPinTaskbarCsv is a function that was customized for our environment, I only include it as an example only, if someone wanted to roll this out in a login script to ensure the users have all of the apps they need pinned, it would need a good deal of modification and simplification. It has some functions which are not included that check group membership and reformat strings to expand variables, which are not required. After pinning the applications, it displays more reliably if explorer is restarted, and the Csv function will restart explorer if any items are pinned.
Function Set-PinTaskbar {
Param (
[parameter(Mandatory=$True, HelpMessage="Target item to pin")]
[ValidateNotNullOrEmpty()]
[string] $Target
,
[Parameter(Mandatory=$False, HelpMessage="Target item to unpin")]
[switch]$Unpin
)
If (!(Test-Path $Target)) {
Write-Warning "$Target does not exist"
Break
}
$Reg = #{}
$Reg.Key1 = "*"
$Reg.Key2 = "shell"
$Reg.Key3 = "{:}"
$Reg.Value = "ExplorerCommandHandler"
$Reg.Data = (Get-ItemProperty ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin")).ExplorerCommandHandler
$Reg.Path1 = "HKCU:\SOFTWARE\Classes"
$Reg.Path2 = Join-Path $Reg.Path1 $Reg.Key1
$Reg.Path3 = Join-Path $Reg.Path2 $Reg.Key2
$Reg.Path4 = Join-Path $Reg.Path3 $Reg.Key3
If (!(Test-Path -LiteralPath $Reg.Path2)) {
New-Item -ItemType Directory -Path $Reg.Path1 -Name [System.Management.Automation.WildcardPattern]::Escape($Reg.Key1)
}
If (!(Test-Path -LiteralPath $Reg.Path3)) {
New-Item -ItemType Directory -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path2)) -Name $Reg.Key2
}
If (!(Test-Path -LiteralPath $Reg.Path4)) {
New-Item -ItemType Directory -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path3)) -Name $Reg.Key3
}
Set-ItemProperty -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path4)) -Name $Reg.Value -Value $Reg.Data
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
# Registry key where the pinned items are located
$RegistryKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband"
# Binary registry value where the pinned items are located
$RegistryValue = "FavoritesResolve"
# Gets the contents into an ASCII format
$CurrentPinsProperty = ([system.text.encoding]::ASCII.GetString((Get-ItemProperty -Path $RegistryKey -Name $RegistryValue | Select-Object -ExpandProperty $RegistryValue)))
# Filters the results for only the characters that we are looking for, so that the search will function
[string]$CurrentPinsResults = $CurrentPinsProperty -Replace '[^\x20-\x2f^\x30-\x3a\x41-\x5c\x61-\x7F]+', ''
# Globally Unique Identifiers for common system folders, to replace in the pin results
$Guid = #{}
$Guid.FOLDERID_ProgramFilesX86 = #{
"ID" = "{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}"
"Path" = ${env:ProgramFiles(x86)}
}
$Guid.FOLDERID_ProgramFilesX64 = #{
"ID" = "{6D809377-6AF0-444b-8957-A3773F02200E}"
"Path" = $env:ProgramFiles
}
$Guid.FOLDERID_ProgramFiles = #{
"ID" = "{905e63b6-c1bf-494e-b29c-65b732d3d21a}"
"Path" = $env:ProgramFiles
}
$Guid.FOLDERID_System = #{
"ID" = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}"
"Path" = Join-Path $env:WINDIR "System32"
}
$Guid.FOLDERID_Windows = #{
"ID" = "{F38BF404-1D43-42F2-9305-67DE0B28FC23}"
"Path" = $env:WINDIR
}
ForEach ($GuidEntry in $Guid.Keys) {
$CurrentPinsResults = $CurrentPinsResults -replace $Guid.$GuidEntry.ID,$Guid.$GuidEntry.Path
}
$Split = $CurrentPinsResults -split ('C:')
$SplitOutput = #()
# Process each path entry, remove invalid characters, test to determine if the path is valid
ForEach ($Entry in $Split) {
If ($Entry.Substring(0,1) -eq '\') {
# Get a list of invalid path characters
$InvalidPathCharsRegEx = [IO.Path]::GetInvalidPathChars() -join ''
$InvalidPathChars = "[{0}]" -f [RegEx]::Escape($InvalidPathCharsRegEx)
$EntryProcessedPhase1 = "C:" + ($Entry -replace $InvalidPathChars)
$EntryProcessedPhase2 = $null
# Remove characters from the path until it is resolvable
ForEach ($Position in $EntryProcessedPhase1.Length .. 1) {
If (Test-Path $EntryProcessedPhase1.Substring(0,$Position)) {
$EntryProcessedPhase2 = $EntryProcessedPhase1.Substring(0,$Position)
Break
}
}
# If the path resolves, add it to the array of paths
If ($EntryProcessedPhase2) {
$SplitOutput += $EntryProcessedPhase2
}
}
}
$PinnedItems = #()
$Shell = New-Object -ComObject WScript.Shell
ForEach ($Path in $SplitOutput) {
# Determines if the entry in the registry is a link in the standard folder, if it is, resolve the path of the shortcut and add it to the array of pinnned items
If ((Split-Path $Path) -eq (Join-Path $env:USERPROFILE "AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar")) {
$Shell.CreateShortcut($Path).TargetPath
$PinnedItems += $Shell.CreateShortcut($Path).TargetPath
}
Else {
# If the link or executable is not in the taskbar folder, add it directly
$PinnedItems += $Path
}
}
# Unpin if the application is pinned
If ($Unpin.IsPresent) {
If ($PinnedItems -contains $Target) {
$Item.InvokeVerb("{:}")
Write-Host "Unpinning application $Target"
}
}
Else {
# Only pin the application if it hasn't been pinned
If ($PinnedItems -notcontains $Target) {
$Item.InvokeVerb("{:}")
Write-Host "Pinning application $Target"
}
}
# Remove the registry key and subkeys required to pin the application
If (Test-Path $Reg.Path3) {
Remove-Item -LiteralPath $Reg.Path3 -Recurse
}
}
Function Set-PinTaskbarCsv {
Param (
[Parameter(Mandatory=$true)]
$PinHashTable
,
[Parameter(Mandatory=$true)]
$UnpinHashTable
)
$Organization = "LIHC"
$RootRegistry = "HKCU:\Software\" + $Organization
$RootRegistryPinned = Join-Path $RootRegistry "Pinned"
# Unpin applications from taskbar
ForEach ($Entry in $UnpinHashTable.Keys) {
$Location = Format-VariablesString -String $UnpinHashTable.$Entry.Location
Add-Log "Taskbar app unpinned" $Location
Set-PinTaskbar -Target $Location -Unpin
}
# Pin applications to taskbar
$Groups = #("Group1","Group2","Group3","Group4","Group5")
ForEach ($Entry in $PinHashTable.Keys) {
$Entry
$Location = Format-VariablesString -String $PinHashTable.$Entry.Location
$ToTaskbar = [string]::IsNullOrWhiteSpace($PinHashTable.$Entry.Group1)
ForEach ($Group in $Groups) {
If (!([string]::IsNullOrWhiteSpace($PinHashTable.$Entry.$Group))) {
$ToTaskbar = (Get-UserGroups -Username $env:USERNAME -Group $PinHashTable.$Entry.$Group) -or $ToTaskbar
}
}
If (!([string]::IsNullOrWhiteSpace($PinHashTable.$Entry.TestPath))) {
$ToTaskbar = ((Test-Path $PinHashTable.$Entry.TestPath) -or (Test-Path $PinHashTable.$Entry.TestPath2)) -and $true
}
If ($ToTaskbar -and (Test-Path $Location) -and (!(Get-ItemProperty $RootRegistryPinned $Location -ErrorAction SilentlyContinue))) {
#Set-AppPinTaskbar -Application $Location
Set-PinTaskbar -Target $Location
Add-Log "Taskbar app Pinned" $Location
New-ItemProperty -Path $RootRegistryPinned -Name $Location 2>&1 > $null
$Status = $true
}
}
If ($Status) {
Get-Process -Name explorer | Stop-Process
Start-Process -FilePath explorer.exe
}
}

Clears Tags in Set-AzureRmTag using Automation Account

I'm tying to change a value on a tag, using an automation script. The users will have a startup script, which will change the shutdown tag key from true to false.
When I set the tags individually using the script below it sets the tag value to false. The current setting is true.
When I use the automation script it wipes all the tags, however If I specify the vm in the script the automaton account works and changes the key value from false to true.
I can't see what I'm missing. This is from a webhook and is running as a powershell script, not a workflow.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[object]$WebhookData
)
Write-Output "------------------------------------------------"
Write-Output "`nConnecting to Azure Automation"
$Connection = Get-AutomationConnection -Name AzureRunAsConnection
Add-AzureRMAccount -ServicePrincipal -Tenant $Connection.TenantID `
-ApplicationId $Connection.ApplicationID -CertificateThumbprint $Connection.CertificateThumbprint
$RunbookVersion = "0.0.17"
$timeStartUTC = (Get-Date).ToUniversalTime()
Write-Output "Workflow started: Runbook Version is $RunbookVersion"
Write-Output "System time is: $(Get-Date)"
Write-Output "`nGetting tagged resources"
Write-Output "------------------------------------------------"
$ResourceGroupFilter = ""
$SupportedEnvironments = "DEV, Test, PREProd, Prod"
$isWebhookDataNull = $WebhookData -eq $null
Write-Output "Is webhook data null ? : $($isWebhookDataNull)"
# If runbook was called from Webhook, WebhookData will not be null.
If ($WebhookData -ne $null) {
# Collect properties of WebhookData
$WebhookName = $WebhookData.WebhookName
$WebhookHeaders = $WebhookData.RequestHeader
$WebhookBody = $WebhookData.RequestBody
$body = $WebhookBody | ConvertFrom-Json
$UserEmail = $body.user.email
Write-Output "Runbook started from webhook '$WebhookName' by '$($body.user.email)' for environment '$($body.environment)'"
Write-Output "Message body: " $WebhookBody
}
else {
Write-Error "Runbook mean to be started only from webhook."
}
If ($body.environment.ToUpper() -eq 'DEV') {
$ResourceGroupFilter = 'The-DEV-RG'
}
if ($ResourceGroupFilter -eq "") {
Exit 1
}
if($VMRG -eq ''){
Write-Output "No resource groups matched for selected environment. Webhook cant progress further, exiting.."
Write-Error "No resource groups matched for selected environment. Webhook cant progress further, exiting.."
Exit 1
}
$rgs = Get-AzureRmResourceGroup | Where-Object {$_.ResourceGroupName -like "*$rg*"}
foreach ($rg in $rgs)
{
$vms = Get-AzureRmVm -ResourceGroupName $rg.ResourceGroupName
$vms.ForEach({
$tags = $_.Tags
$tags['ShutdownSchedule_AllowStop'] = "$False";
Set-AzureRmResource -ResourceId $_.Id -Tag $tags -Force -Verbose
})
}
ForEach ($vm in $vms) {
Start-AzureRmVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Verbose
}
Thanks in advance :)
The root reason is your local Azure Power Shell is latest version, but in Azure automation account, it is not latest version. I test in my lab, older version does not support this.
You need upgrade Azure Power Shell version. More information about this please see this answer.