Azure Automation RunBook from Another RunBook - azure

I have a runbook name "RB_ConnectSQL"
workflow RB_ConnectSQL
{
[OutputType([string])]
param
(
[Parameter(Mandatory=$true)] [string] $SqlServer,
[Parameter(Mandatory=$false)] [int] $SqlServerPort = 1433,
[Parameter(Mandatory=$true)] [string] $Database,
[Parameter(Mandatory=$true)] [string] $Procedure,
[Parameter(Mandatory=$true)] [string] $SqlCredentialName
)
$SqlCredential = Get-AutomationPSCredential -Name $SqlCredentialName
$SqlUsername = $SqlCredential.UserName
$SqlPass = $SqlCredential.GetNetworkCredential().Password
inlinescript
{
$haveError = 0
$DatabaseConnection = New-Object System.Data.SqlClient.SqlConnection("Server=tcp:$using:SqlServer,$using:SqlServerPort; Database=$using:Database; User ID=$using:SqlUsername;Password=$using:SqlPass; Trusted_Connection=False; Encrypt=True; Connect Timeout=7200;")
$outputDataTable = New-Object System.Data.DataTable
[string[]] $ColumnNames
try
{
$DatabaseConnection.Open()
$Cmd=new-object system.Data.SqlClient.SqlCommand
$Cmd.Connection = $DatabaseConnection
$Cmd.CommandText = 'EXEC ' + $using:Procedure + ';'
$Cmd.CommandTimeout = 7200
$sqlDataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $Cmd
$dataSet = New-Object System.Data.DataSet
Write-Output($Cmd.CommandText)
$sqlDataAdapter.Fill($dataSet) | out-null
if ($dataSet.Tables[0].Rows.Count -gt 0)
{
$outputDataTable = $dataSet.Tables[0]
}
else
{
$outputDataTable = "SQL Stroc Proc Executed”
}
}
catch
{
#write your own error handling code here.
#if required send error message in email.
}
finally
{
if ($Cmd -ne $null)
{
$Cmd.Dispose
}
$DatabaseConnection.Close()
$DatabaseConnection.Dispose()
}
}
}
And another runnbook name "RB_Daily_Transaction_Summary_Record"
workflow RB_Daily_Transaction_Summary_Record
{
$dataTable = RB_ConnectSQL -SqlServer 'blahblah.database.windows.net' -Database 'blahDev' -Procedure 'sp_Daily_Transaction_Summary_Record' -SqlCredentialName 'blahCredential'
Write-Output($dataTable)
}
The runbook "RB_Daily_Transaction_Summary_Record" suppose to call "RB_ConnectSQL" and pass in the required parameter so that will execute the Store Procedure in Azure SQL Server.
However I get the error
At line:78 char:17
+ -SqlServer 'blahblah.database.windows.net'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Cannot find the '-SqlServer' command. If this command is defined as a workflow, ensure it is defined before the workflow that calls it. If it is a command intended to run directly within Windows PowerShell (or is not available on this system), place it in an InlineScript: 'InlineScript { -SqlServer }'
May I know is there any mistake I make on the runbook?

When you want to separate a command into many lines, please add white-space and backquote at the end of each line, except for the last line.
In your case, it should work using the following format:
workflow RB_Daily_Transaction_Summary_Record
{
$dataTable = RB_ConnectSQL
-SqlServer 'blahblah.database.windows.net' `
-Database 'blahDev' `
-Procedure 'sp_Daily_Transaction_Summary_Record' `
-SqlCredentialName 'blahCredential'
Write-Output($dataTable)
}

I found out that the issue was I separate my code for readability become
workflow RB_Daily_Transaction_Summary_Record
{
$dataTable = RB_ConnectSQL
-SqlServer 'blahblah.database.windows.net'
-Database 'blahDev'
-Procedure 'sp_Daily_Transaction_Summary_Record'
-SqlCredentialName 'blahCredential'
Write-Output($dataTable)
}
But the new line seem like break the code.
I rewrite it as:
workflow RB_Daily_Transaction_Summary_Record
{
$dataTable = RB_ConnectSQL -SqlServer 'blahblah.database.windows.net' -Database 'blahDev' -Procedure 'sp_Daily_Transaction_Summary_Record' -SqlCredentialName 'blahCredential'
Write-Output($dataTable)
}
Then the error gone and it work well!

Related

How to import automatically Az Powershell modules in azure automation account?

I am trying to install az powershell modules from my automation runbook with powershell script. But i am failing to do that. I have tried the solution in a similar topic which is here but it didn't work for me. The code is below:
$AAccName = "my-aa"
$RGName = "my-rg"
$deps1 = #("Az.Accounts","Az.Profile")
foreach($dep in $deps1){
$module = Find-Module -Name $dep
$link = $module.RepositorySourceLocation + "/package/" + $module.Name + "/" + $module.Version
New-AzAutomationModule -AutomationAccountName $AAccName -Name $module.Name -ContentLinkUri $link -ResourceGroupName $RGName
}
The error , i get:
Exception calling "ShouldContinue" with "2" argument(s): "A command
that prompts the user failed because the host program or the command
type does not support user interaction. The host was attempting to
request confirmation with the following message: PowerShellGet
requires NuGet provider version '2.8.5.201' or newer to interact with
NuGet-based repositories. The NuGet provider must be available in
'C:\Program Files\PackageManagement\ProviderAssemblies' or
'C:\Users\Client\AppData\Roaming\PackageManagement\ProviderAssemblies'.
You can also install the NuGet provider by running
'Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201
-Force'. Do you want PowerShellGet to install and import the NuGet provider now?" At C:\Program
Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:7455
char:8 + if($Force -or
$psCmdlet.ShouldContinue($shouldContinueQueryMessag ... +
I have executed the command given in this message but i get this error:
Install-PackageProvider : No match was found for the specified search
criteria for the provider 'NuGet'. The package provider requires
'PackageManagement' and 'Provider' tags.
Do you have any idea how to add module in Azure Automation Account with script?
Update:
When i use Import-Module -Name Az.Profile -Force command, I get this error:
Import-Module : The specified module 'Az.Profile' was not loaded
because no valid module file was found in any module directory.
This should be because that the module is not installed on the module directory. When i manually add the module from the module gallery, it works.
Check this MS recommended PS script to update modules in Automation account. It's reference is given here: Use the update runbook to update a specific module version.
Script:
<#
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
#>
<#
.SYNOPSIS
Update Azure PowerShell modules in an Azure Automation account.
.DESCRIPTION
This Azure Automation runbook updates Azure PowerShell modules imported into an
Azure Automation account with the module versions published to the PowerShell Gallery.
Prerequisite: an Azure Automation account with an Azure Run As account credential.
.PARAMETER ResourceGroupName
The Azure resource group name.
.PARAMETER AutomationAccountName
The Azure Automation account name.
.PARAMETER SimultaneousModuleImportJobCount
(Optional) The maximum number of module import jobs allowed to run concurrently.
.PARAMETER AzureModuleClass
(Optional) The class of module that will be updated (AzureRM or Az)
If set to Az, this script will rely on only Az modules to update other modules.
Set this to Az if your runbooks use only Az modules to avoid conflicts.
.PARAMETER AzureEnvironment
(Optional) Azure environment name.
.PARAMETER Login
(Optional) If $false, do not login to Azure.
.PARAMETER ModuleVersionOverrides
(Optional) Module versions to use instead of the latest on the PowerShell Gallery.
If $null, the currently published latest versions will be used.
If not $null, must contain a JSON-serialized dictionary, for example:
'{ "AzureRM.Compute": "5.8.0", "AzureRM.Network": "6.10.0" }'
or
#{ 'AzureRM.Compute'='5.8.0'; 'AzureRM.Network'='6.10.0' } | ConvertTo-Json
.PARAMETER PsGalleryApiUrl
(Optional) PowerShell Gallery API URL.
.LINK
https://learn.microsoft.com/en-us/azure/automation/automation-update-azure-modules
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "")]
param(
[Parameter(Mandatory = $true)]
[string] $ResourceGroupName,
[Parameter(Mandatory = $true)]
[string] $AutomationAccountName,
[int] $SimultaneousModuleImportJobCount = 10,
[string] $AzureModuleClass = 'AzureRM',
[string] $AzureEnvironment = 'AzureCloud',
[bool] $Login = $true,
[string] $ModuleVersionOverrides = $null,
[string] $PsGalleryApiUrl = 'https://www.powershellgallery.com/api/v2'
)
$ErrorActionPreference = "Continue"
#region Constants
$script:AzureRMProfileModuleName = "AzureRM.Profile"
$script:AzureRMAutomationModuleName = "AzureRM.Automation"
$script:GetAzureRmAutomationModule = "Get-AzureRmAutomationModule"
$script:NewAzureRmAutomationModule = "New-AzureRmAutomationModule"
$script:AzAccountsModuleName = "Az.Accounts"
$script:AzAutomationModuleName = "Az.Automation"
$script:GetAzAutomationModule = "Get-AzAutomationModule"
$script:NewAzAutomationModule = "New-AzAutomationModule"
$script:AzureSdkOwnerName = "azure-sdk"
#endregion
#region Functions
function ConvertJsonDictTo-HashTable($JsonString) {
try{
$JsonObj = ConvertFrom-Json $JsonString -ErrorAction Stop
} catch [System.ArgumentException] {
throw "Unable to deserialize the JSON string for parameter ModuleVersionOverrides: ", $_
}
$Result = #{}
foreach ($Property in $JsonObj.PSObject.Properties) {
$Result[$Property.Name] = $Property.Value
}
$Result
}
# Use the Run As connection to login to Azure
function Login-AzureAutomation([bool] $AzModuleOnly) {
try {
$RunAsConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
Write-Output "Logging in to Azure ($AzureEnvironment)..."
if (!$RunAsConnection.ApplicationId) {
$ErrorMessage = "Connection 'AzureRunAsConnection' is incompatible type."
throw $ErrorMessage
}
if ($AzModuleOnly) {
Connect-AzAccount `
-ServicePrincipal `
-TenantId $RunAsConnection.TenantId `
-ApplicationId $RunAsConnection.ApplicationId `
-CertificateThumbprint $RunAsConnection.CertificateThumbprint `
-Environment $AzureEnvironment
Select-AzSubscription -SubscriptionId $RunAsConnection.SubscriptionID | Write-Verbose
} else {
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $RunAsConnection.TenantId `
-ApplicationId $RunAsConnection.ApplicationId `
-CertificateThumbprint $RunAsConnection.CertificateThumbprint `
-Environment $AzureEnvironment
Select-AzureRmSubscription -SubscriptionId $RunAsConnection.SubscriptionID | Write-Verbose
}
} catch {
if (!$RunAsConnection) {
$RunAsConnection | fl | Write-Output
Write-Output $_.Exception
$ErrorMessage = "Connection 'AzureRunAsConnection' not found."
throw $ErrorMessage
}
throw $_.Exception
}
}
# Checks the PowerShell Gallery for the latest available version for the module
function Get-ModuleDependencyAndLatestVersion([string] $ModuleName) {
$ModuleUrlFormat = "$PsGalleryApiUrl/Search()?`$filter={1}&searchTerm=%27{0}%27&targetFramework=%27%27&includePrerelease=false&`$skip=0&`$top=40"
$ForcedModuleVersion = $ModuleVersionOverridesHashTable[$ModuleName]
$CurrentModuleUrl =
if ($ForcedModuleVersion) {
$ModuleUrlFormat -f $ModuleName, "Version%20eq%20'$ForcedModuleVersion'"
} else {
$ModuleUrlFormat -f $ModuleName, 'IsLatestVersion'
}
$SearchResult = Invoke-RestMethod -Method Get -Uri $CurrentModuleUrl -UseBasicParsing
if (!$SearchResult) {
Write-Verbose "Could not find module $ModuleName on PowerShell Gallery. This may be a module you imported from a different location. Ignoring this module"
} else {
if ($SearchResult.Length -and $SearchResult.Length -gt 1) {
$SearchResult = $SearchResult | Where-Object { $_.title.InnerText -eq $ModuleName }
}
if (!$SearchResult) {
Write-Verbose "Could not find module $ModuleName on PowerShell Gallery. This may be a module you imported from a different location. Ignoring this module"
} else {
$PackageDetails = Invoke-RestMethod -Method Get -UseBasicParsing -Uri $SearchResult.id
# Ignore the modules that are not published as part of the Azure SDK
if ($PackageDetails.entry.properties.Owners -ne $script:AzureSdkOwnerName) {
Write-Warning "Module : $ModuleName is not part of azure sdk. Ignoring this."
} else {
$ModuleVersion = $PackageDetails.entry.properties.version
$Dependencies = $PackageDetails.entry.properties.dependencies
#($ModuleVersion, $Dependencies)
}
}
}
}
function Get-ModuleContentUrl($ModuleName) {
$ModuleContentUrlFormat = "$PsGalleryApiUrl/package/{0}"
$VersionedModuleContentUrlFormat = "$ModuleContentUrlFormat/{1}"
$ForcedModuleVersion = $ModuleVersionOverridesHashTable[$ModuleName]
if ($ForcedModuleVersion) {
$VersionedModuleContentUrlFormat -f $ModuleName, $ForcedModuleVersion
} else {
$ModuleContentUrlFormat -f $ModuleName
}
}
# Imports the module with given version into Azure Automation
function Import-AutomationModule([string] $ModuleName, [bool] $UseAzModule = $false) {
$NewAutomationModule = $null
$GetAutomationModule = $null
if ($UseAzModule) {
$GetAutomationModule = $script:GetAzAutomationModule
$NewAutomationModule = $script:NewAzAutomationModule
} else {
$GetAutomationModule = $script:GetAzureRmAutomationModule
$NewAutomationModule = $script:NewAzureRmAutomationModule
}
$LatestModuleVersionOnGallery = (Get-ModuleDependencyAndLatestVersion $ModuleName)[0]
$ModuleContentUrl = Get-ModuleContentUrl $ModuleName
# Find the actual blob storage location of the module
do {
$ModuleContentUrl = (Invoke-WebRequest -Uri $ModuleContentUrl -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore).Headers.Location
} while (!$ModuleContentUrl.Contains(".nupkg"))
$CurrentModule = & $GetAutomationModule `
-Name $ModuleName `
-ResourceGroupName $ResourceGroupName `
-AutomationAccountName $AutomationAccountName
if ($CurrentModule.Version -eq $LatestModuleVersionOnGallery) {
Write-Output "Module : $ModuleName is already present with version $LatestModuleVersionOnGallery. Skipping Import"
} else {
Write-Output "Importing $ModuleName module of version $LatestModuleVersionOnGallery to Automation"
& $NewAutomationModule `
-ResourceGroupName $ResourceGroupName `
-AutomationAccountName $AutomationAccountName `
-Name $ModuleName `
-ContentLink $ModuleContentUrl > $null
}
}
# Parses the dependency got from PowerShell Gallery and returns name and version
function GetModuleNameAndVersionFromPowershellGalleryDependencyFormat([string] $Dependency) {
if ($null -eq $Dependency) {
throw "Improper dependency format"
}
$Tokens = $Dependency -split":"
if ($Tokens.Count -ne 3) {
throw "Improper dependency format"
}
$ModuleName = $Tokens[0]
$ModuleVersion = $Tokens[1].Trim("[","]")
#($ModuleName, $ModuleVersion)
}
# Validates if the given list of modules has already been added to the module import map
function AreAllModulesAdded([string[]] $ModuleListToAdd) {
$Result = $true
foreach ($ModuleToAdd in $ModuleListToAdd) {
$ModuleAccounted = $false
# $ModuleToAdd is specified in the following format:
# ModuleName:ModuleVersionSpecification:
# where ModuleVersionSpecification follows the specifiation
# at https://learn.microsoft.com/en-us/nuget/reference/package-versioning#version-ranges-and-wildcards
# For example:
# AzureRm.profile:[4.0.0]:
# or
# AzureRm.profile:3.0.0:
# In any case, the dependency version specification is always separated from the module name with
# the ':' character. The explicit intent of this runbook is to always install the latest module versions,
# so we want to completely ignore version specifications here.
$ModuleNameToAdd = $ModuleToAdd -replace '\:.*', ''
foreach($AlreadyIncludedModules in $ModuleImportMapOrder) {
if ($AlreadyIncludedModules -contains $ModuleNameToAdd) {
$ModuleAccounted = $true
break
}
}
if (!$ModuleAccounted) {
$Result = $false
break
}
}
$Result
}
# Creates a module import map. This is a 2D array of strings so that the first
# element in the array consist of modules with no dependencies.
# The second element only depends on the modules in the first element, the
# third element only dependes on modules in the first and second and so on.
function Create-ModuleImportMapOrder([bool] $AzModuleOnly) {
$ModuleImportMapOrder = $null
$ProfileOrAccountsModuleName = $null
$GetAutomationModule = $null
# Use the relevant module class to avoid conflicts
if ($AzModuleOnly) {
$ProfileOrAccountsModuleName = $script:AzAccountsModuleName
$GetAutomationModule = $script:GetAzAutomationModule
} else {
$ProfileOrAccountsModuleName = $script:AzureRmProfileModuleName
$GetAutomationModule = $script:GetAzureRmAutomationModule
}
# Get all the non-conflicting modules in the current automation account
$CurrentAutomationModuleList = & $GetAutomationModule `
-ResourceGroupName $ResourceGroupName `
-AutomationAccountName $AutomationAccountName |
?{
($AzModuleOnly -and ($_.Name -eq 'Az' -or $_.Name -like 'Az.*')) -or
(!$AzModuleOnly -and ($_.Name -eq 'AzureRM' -or $_.Name -like 'AzureRM.*' -or
$_.Name -eq 'Azure' -or $_.Name -like 'Azure.*'))
}
# Get the latest version of the AzureRM.Profile OR Az.Accounts module
$VersionAndDependencies = Get-ModuleDependencyAndLatestVersion $ProfileOrAccountsModuleName
$ModuleEntry = $ProfileOrAccountsModuleName
$ModuleEntryArray = ,$ModuleEntry
$ModuleImportMapOrder += ,$ModuleEntryArray
do {
$NextAutomationModuleList = $null
$CurrentChainVersion = $null
# Add it to the list if the modules are not available in the same list
foreach ($Module in $CurrentAutomationModuleList) {
$Name = $Module.Name
Write-Verbose "Checking dependencies for $Name"
$VersionAndDependencies = Get-ModuleDependencyAndLatestVersion $Module.Name
if ($null -eq $VersionAndDependencies) {
continue
}
$Dependencies = $VersionAndDependencies[1].Split("|")
$AzureModuleEntry = $Module.Name
# If the previous list contains all the dependencies then add it to current list
if ((-not $Dependencies) -or (AreAllModulesAdded $Dependencies)) {
Write-Verbose "Adding module $Name to dependency chain"
$CurrentChainVersion += ,$AzureModuleEntry
} else {
# else add it back to the main loop variable list if not already added
if (!(AreAllModulesAdded $AzureModuleEntry)) {
Write-Verbose "Module $Name does not have all dependencies added as yet. Moving module for later import"
$NextAutomationModuleList += ,$Module
}
}
}
$ModuleImportMapOrder += ,$CurrentChainVersion
$CurrentAutomationModuleList = $NextAutomationModuleList
} while ($null -ne $CurrentAutomationModuleList)
$ModuleImportMapOrder
}
# Wait and confirm that all the modules in the list have been imported successfully in Azure Automation
function Wait-AllModulesImported(
[Collections.Generic.List[string]] $ModuleList,
[int] $Count,
[bool] $UseAzModule = $false) {
$GetAutomationModule = if ($UseAzModule) {
$script:GetAzAutomationModule
} else {
$script:GetAzureRmAutomationModule
}
$i = $Count - $SimultaneousModuleImportJobCount
if ($i -lt 0) { $i = 0 }
for ( ; $i -lt $Count; $i++) {
$Module = $ModuleList[$i]
Write-Output ("Checking import Status for module : {0}" -f $Module)
while ($true) {
$AutomationModule = & $GetAutomationModule `
-Name $Module `
-ResourceGroupName $ResourceGroupName `
-AutomationAccountName $AutomationAccountName
$IsTerminalProvisioningState = ($AutomationModule.ProvisioningState -eq "Succeeded") -or
($AutomationModule.ProvisioningState -eq "Failed")
if ($IsTerminalProvisioningState) {
break
}
Write-Verbose ("Module {0} is getting imported" -f $Module)
Start-Sleep -Seconds 30
}
if ($AutomationModule.ProvisioningState -ne "Succeeded") {
Write-Error ("Failed to import module : {0}. Status : {1}" -f $Module, $AutomationModule.ProvisioningState)
} else {
Write-Output ("Successfully imported module : {0}" -f $Module)
}
}
}
# Uses the module import map created to import modules.
# It will only import modules from an element in the array if all the modules
# from the previous element have been added.
function Import-ModulesInAutomationAccordingToDependency([string[][]] $ModuleImportMapOrder, [bool] $UseAzModule) {
foreach($ModuleList in $ModuleImportMapOrder) {
$i = 0
Write-Output "Importing Array of modules : $ModuleList"
foreach ($Module in $ModuleList) {
Write-Verbose ("Importing module : {0}" -f $Module)
Import-AutomationModule -ModuleName $Module -UseAzModule $UseAzModule
$i++
if ($i % $SimultaneousModuleImportJobCount -eq 0) {
# It takes some time for the modules to start getting imported.
# Sleep for sometime before making a query to see the status
Start-Sleep -Seconds 20
Wait-AllModulesImported -ModuleList $ModuleList -Count $i -UseAzModule $UseAzModule
}
}
if ($i -lt $SimultaneousModuleImportJobCount) {
Start-Sleep -Seconds 20
Wait-AllModulesImported -ModuleList $ModuleList -Count $i -UseAzModule $UseAzModule
}
}
}
function Update-ProfileAndAutomationVersionToLatest([string] $AutomationModuleName) {
# Get the latest azure automation module version
$VersionAndDependencies = Get-ModuleDependencyAndLatestVersion $AutomationModuleName
# Automation only has dependency on profile
$ModuleDependencies = GetModuleNameAndVersionFromPowershellGalleryDependencyFormat $VersionAndDependencies[1]
$ProfileModuleName = $ModuleDependencies[0]
# Create web client object for downloading data
$WebClient = New-Object System.Net.WebClient
# Download AzureRM.Profile to temp location
$ModuleContentUrl = Get-ModuleContentUrl $ProfileModuleName
$ProfileURL = (Invoke-WebRequest -Uri $ModuleContentUrl -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore).Headers.Location
$ProfilePath = Join-Path $env:TEMP ($ProfileModuleName + ".zip")
$WebClient.DownloadFile($ProfileURL, $ProfilePath)
# Download AzureRM.Automation to temp location
$ModuleContentUrl = Get-ModuleContentUrl $AutomationModuleName
$AutomationURL = (Invoke-WebRequest -Uri $ModuleContentUrl -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore).Headers.Location
$AutomationPath = Join-Path $env:TEMP ($AutomationModuleName + ".zip")
$WebClient.DownloadFile($AutomationURL, $AutomationPath)
# Create folder for unzipping the Module files
$PathFolderName = New-Guid
$PathFolder = Join-Path $env:TEMP $PathFolderName
# Unzip files
$ProfileUnzipPath = Join-Path $PathFolder $ProfileModuleName
Expand-Archive -Path $ProfilePath -DestinationPath $ProfileUnzipPath -Force
$AutomationUnzipPath = Join-Path $PathFolder $AutomationModuleName
Expand-Archive -Path $AutomationPath -DestinationPath $AutomationUnzipPath -Force
# Import modules
Import-Module (Join-Path $ProfileUnzipPath ($ProfileModuleName + ".psd1")) -Force -Verbose
Import-Module (Join-Path $AutomationUnzipPath ($AutomationModuleName + ".psd1")) -Force -Verbose
}
#endregion
#region Main body
if ($ModuleVersionOverrides) {
$ModuleVersionOverridesHashTable = ConvertJsonDictTo-HashTable $ModuleVersionOverrides
} else {
$ModuleVersionOverridesHashTable = #{}
}
$UseAzModule = $null
$AutomationModuleName = $null
# We want to support updating Az modules. This means this runbook should support upgrading using only Az modules
if ($AzureModuleClass -eq "Az") {
$UseAzModule = $true
$AutomationModuleName = $script:AzAutomationModuleName
} elseif ( $AzureModuleClass -eq "AzureRM") {
$UseAzModule = $false
$AutomationModuleName = $script:AzureRMAutomationModuleName
} else {
Write-Error "Invalid AzureModuleClass: '$AzureModuleClass'. Must be either Az or AzureRM" -ErrorAction Stop
}
# Import the latest version of the Az automation and accounts version to the local sandbox
Update-ProfileAndAutomationVersionToLatest $AutomationModuleName
if ($Login) {
Login-AzureAutomation $UseAzModule
}
$ModuleImportMapOrder = Create-ModuleImportMapOrder $UseAzModule
Import-ModulesInAutomationAccordingToDependency $ModuleImportMapOrder $UseAzModule
#endregion
Further, if you want to refer to another approach, you can check this script.
Here is Reddit question regarding the same.
I have used azure devops to install the modules with powershell. I was trying to install it within the runbook but that didn't work so i kind of changed the method.
Here is the code:
#necessary modules list
$deps1 = #("Az.Accounts","Az.Storage","Az.Compute")
foreach($dep in $deps1){
$module = Find-Module -Name $dep
$link = $module.RepositorySourceLocation + "/package/" + $module.Name + "/" + $module.Version
New-AzAutomationModule -AutomationAccountName $AutomationAccountName -Name $module.Name -ContentLinkUri $link -ResourceGroupName $ResourceGroupName
if ($dep -eq "Az.Accounts") {
#Az.Accounts is a dependency for Az.Storage and Az.Compute modules
Write-Host "Sleeping for 180 sec in order to wait the installation of the Az.Accounts module"
Start-Sleep 180
}
}
This answer in this post helped me for the code.

Azure Powershell Tagging VM's from CSV file

I'm quite new to Powershell scripting. I'm trying to generate tags for azure vm's from a CSV file.
Inside the CSV I have the following column headings:
VMName
Application
SubCat
Environment
AppOwner
Location
I've got a test CSV which literally has the following data in it:
MattTestVM, TestApp, TestSubapp, Dev, Matt, UK South
I'm not sure what i've put wrong in my code to get it to add the tags.
Code
#Set Credentials
$cred = Get-credential
# Sign-in with Azure account credentials
add-azurermaccount -credential $cred
# Select Azure Subscription
$subscriptionId = (Get-AzureRmSubscription | Out-GridView -Title "Select an Azure Subscription ..." -PassThru).SubscriptionId
#Select specified subscription ID
Select-AzureRmSubscription -SubscriptionId $subscriptionId
$InputCSVFilePath = "C:\test\Tagging1.csv"
#Start loop
foreach ($eachRecord in $InputCSVFilePath)
{
$VMName = $eachrecord.VMName
$Application = $eachrecord.Application
$SubCat = $eachrecord.SubCat
$Environment = $eachrecord.Environment
$AppOwner = $eachrecord.AppOwner
$Location = $eachrecord.Location
$r = Get-AzureRmResource -ResourceId $ResourceId -ErrorAction Continue
if($r -ne $null)
{
if($r.tags)
{
# Tag - Application
if($r.Tags.ContainsKey("Application"))
{
$r.Tags["Application"] = $Application
}
else
{
$r.Tags.Add("Application", $Application)
}
# Tag - SubCat
if($r.Tags.ContainsKey("subCat"))
{
$r.Tags["subCat"] = $subCat
}
else
{
$r.Tags.Add("subCat", $subCat)
}
# Tag - Environment
if($r.Tags.ContainsKey("Environment"))
{
$r.Tags["Environment"] = $Environment
}
else
{
$r.Tags.Add("Environment", $Environment)
}
# Tag - AppOwner
if($r.Tags.ContainsKey("AppOwner"))
{
$r.Tags["AppOwner"] = $AppOwner
}
else
{
$r.Tags.Add("AppOwner", $AppOwner)
}
# Tag - Location
if($r.Tags.ContainsKey("Location"))
{
$r.Tags["Location"] = $Location
}
else
{
$r.Tags.Add("Location", $Location)
}
#Setting the tags on the resource
Set-AzureRmResource -Tag $r.Tags -ResourceId $r.ResourceId -Force
}
else
{
#Setting the tags on a resource which doesn't have tags
Set-AzureRmResource -Tag #{ Application=$Application; subCat=$subCat; Environment=$Environment; AppOwner=$AppOwner; Location=$Location } -ResourceId $r.ResourceId -Force
}
}
else
{
Write-Host "Resource Not Found with Resource Id: " + $ResourceId
}
}
Error message
Get-AzureRmResource : Cannot validate argument on parameter 'ResourceId'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:10 char:43
+ $r = Get-AzureRmResource -ResourceId $ResourceId -ErrorAction Co ...
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-AzureRmResource], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.GetAzureResourceCmdlet
OK, first thing's first: You can't do this without the ResourceGroupName, and the best way to add it in your case is as a column in your csv.
Secondly, everybody starts somewhere so never apologize. :) I commented about most of the issues I fixed in the code below. Your biggest problems were a) you didn't actually load the data from the CSV (you only created a variable with the file path in it), and b) you were attempting to use $ResourceID without ever populating it.
I cleaned things up a bit and it works now, and will throw an error if it can't find the VM. Here are the contents of my test.csv, with one good VM and one that doesn't exist.
VMName,ResourceGroup,Application,SubCat,Environment,AppOwner,Location
test-rm-01,test-rg,Thing,Subs,Dev,Nick,Europe
clambake,mygroup,,,,,
And here's the re-worked script.
# Set Credentials (Fixed this up)
$context = Get-AzureRmContext
if ($context.Account -eq $null) {
Login-AzureRmAccount
}
# Select Azure Subscription
$subscriptionId = (Get-AzureRmSubscription | Out-GridView -Title "Select an Azure Subscription ..." -PassThru).SubscriptionId
#Select specified subscription ID
Select-AzureRmSubscription -SubscriptionId $subscriptionId
$InputCSVFilePath = "C:\Users\Nick\Desktop\test.csv"
################
#
# Here you were missing loading the actual data. Also renamed the items in the loop, nitpicking. :)
#
################
$csvItems = Import-Csv $InputCSVFilePath
################
#Start loop
foreach ($item in $csvItems)
{
################
#
# Change here, you need ResourceGroupName
# Also cleared r because if you don't, it will still have a value if no matching VM is found, which would re-tag the previous item from the loop, instead of throwing an error that the VM was not found
#
################
Clear-Variable r
$r = Get-AzureRmResource -ResourceGroupName $item.ResourceGroup -Name $item.VMName -ErrorAction Continue
################
if ($r -ne $null)
{
if ($r.Tags)
{
# Tag - Application
if ($r.Tags.ContainsKey("Application"))
{
$r.Tags["Application"] = $item.Application
}
else
{
$r.Tags.Add("Application", $item.Application)
}
# Tag - SubCat
if ($r.Tags.ContainsKey("subCat"))
{
$r.Tags["subCat"] = $item.subCat
}
else
{
$r.Tags.Add("subCat", $item.subCat)
}
# Tag - Environment
if ($r.Tags.ContainsKey("Environment"))
{
$r.Tags["Environment"] = $item.Environment
}
else
{
$r.Tags.Add("Environment", $item.Environment)
}
# Tag - AppOwner
if ($r.Tags.ContainsKey("AppOwner"))
{
$r.Tags["AppOwner"] = $item.AppOwner
}
else
{
$r.Tags.Add("AppOwner", $item.AppOwner)
}
# Tag - Location
if ($r.Tags.ContainsKey("Location"))
{
$r.Tags["Location"] = $item.Location
}
else
{
$r.Tags.Add("Location", $item.Location)
}
#Setting the tags on the resource
Set-AzureRmResource -Tag $r.Tags -ResourceId $r.ResourceId -Force
}
else
{
#Setting the tags on a resource which doesn't have tags
Set-AzureRmResource -Tag #{ Application = $Application; subCat = $subCat; Environment = $Environment; AppOwner = $AppOwner; Location = $Location } -ResourceId $r.ResourceId -Force
}
}
else
{
Write-Host "No VM found named $($item.VMName) in ResourceGroup $($item.ResourceGroup)!"
}
}

Cannot find type Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADPasswordCredential:

I am creating Azure Active Directory Application using Azure PowerShell in Visual Studio Code following this article https://sabin.io/blog/adding-an-azure-active-directory-application-and-key-using-powershell/ .
I have modified code to use Az module instead of AzureRM but getting exception
New-Object : Cannot find type [Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADPasswordCredential]: verify that the assembly containing this type is loaded.
PowerShell
function Create-AesManagedObject($key, $IV) {
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
if ($IV) {
if ($IV.getType().Name -eq "String") {
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
}
else {
$aesManaged.IV = $IV
}
}
if ($key) {
if ($key.getType().Name -eq "String") {
$aesManaged.Key = [System.Convert]::FromBase64String($key)
}
else {
$aesManaged.Key = $key
}
}
$aesManaged
}
function Create-AesKey() {
$aesManaged = Create-AesManagedObject
$aesManaged.GenerateKey()
[System.Convert]::ToBase64String($aesManaged.Key)
}
#Create the 44-character key value
$keyValue = Create-AesKey
$psadCredential = New-Object Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADPasswordCredential
$startDate = Get-Date
$psadCredential.StartDate = $startDate
$psadCredential.EndDate = $startDate.AddYears(1)
$psadCredential.KeyId = [guid]::NewGuid()
$psadCredential.Password = $KeyValue
$ApplicationURI = "https://xxx.xxx/xxxx"
New-AzADApplication –DisplayName “MyNewApp2”`
-HomePage $ApplicationURI `
-IdentifierUris $ApplicationURI `
-PasswordCredentials $psadCredential
$keyValue | out-file “c:\someplace\keyvalue.txt”
I need to know how to replace
$psadCredential = New-Object Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADPasswordCredential
with something that is compatible and works in Az module
Try using New-AzureRmADAppCredential instead of New-Object Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADPasswordCredential.
Ref: https://github.com/Azure/azure-powershell/issues/4491
EDIT__________________________________________________
If you choose to, You can enable aliases to work with the new AZ module and still use the old cmdlet names. Ref: https://learn.microsoft.com/en-us/powershell/azure/migrate-from-azurerm-to-az?view=azps-2.4.0
But I think this maybe what you are looking for. New-AzADAppCredential
Ref: https://learn.microsoft.com/en-us/powershell/module/az.resources/new-azadappcredential?view=azps-2.4.0
You can use:
Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential
and it can be imported using
Import-Module Az.Resources
Try this code$psadCredential = New-Object Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential with the article.
The result is as below:

Is there option to auto terminate Azure SQL DW

I am using Azure SQL DW which costs more per an hour. So I want to know is there option for auto terminate SQL DW after an hour or so?
You can pause the Azure Data warehouse and then you only pay for the storage used.
You can automate pausing your DWH by using an Azure automation account and a runbook.
This blog explains the process:
https://blogs.msdn.microsoft.com/allanmiller/2017/09/20/pausing-azure-sql-data-warehouse-using-an-automation-runbook/
Markus
Yes, it is possible.
To save costs, you can pause and resume compute resources on-demand. For example, if you won't be using the database during the night and on weekends, you can pause it during those times, and resume it during the day. You won't be charged for DWUs while the database is paused.
When you pause a database:
Compute and memory resources are returned to the pool of available resources in the data center
DWU costs are zero for the duration of the pause.
Data storage is not affected and your data stays intact.
SQL Data Warehouse cancels all running or queued operations.
To pause a database, use the Suspend-AzureRmSqlDatabase cmdlet.
Suspend-AzureRmSqlDatabase –ResourceGroupName "ResourceGroup1" `
–ServerName "Server01" –DatabaseName "Database02"
To start a database, use the Resume-AzureRmSqlDatabase cmdlet.
Resume-AzureRmSqlDatabase –ResourceGroupName "ResourceGroup1" `
–ServerName "Server01" -DatabaseName "Database02"
More information please refer to this official document.
As Markus Bohse said, you also could use Automation to do this.
Note: You also could write a runbook to start your database.
Update: If you want to use java to do this, please refer to this API document.
public void pauseDataWarehouse()
Update:
You also could Rest API to do this. See this link
POST https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.Sql/servers/{server-name}/databases/{database-name}/pause?api-version=2014-04-01-preview HTTP/1.1
You can use runbooks or powershell script to pause dn resume SQL DWH.
Powershell script is below. If you would like "runbook" let me know
[CmdletBinding(DefaultParametersetName='None')]
Param
(
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureSubscriptionId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureDataWareHouseList="All",
[Parameter(Mandatory=$true)][ValidateSet("Suspend","Resume")]
[String]
$Action
)
function PauseAzureDWH
{
Param
(
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureSubscriptionId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureDataWareHouseList="All",
[Parameter(Mandatory=$true)][ValidateSet("Suspend","Resume")]
[String]
$Action
)
try
{
Login-AzureRmAccount
Select-AzureRmSubscription -SubscriptionId $AzureSubscriptionId
if($AzureDataWareHouseList -ne "All")
{
$AzureDWHList = #()
$AzureDWHTotalList = $AzureDataWareHouseList.Split(",")
foreach($DWHitem in $AzureDWHTotalList)
{
$DWH = "*$DWHitem*"
$DWH = Get-AzureRmResource | Where-Object ResourceName -like $DWH
if($DWH -ne $Null)
{
$dwc = $DWH.ResourceName.split("/")
# splat reused parameter lists
$ThisDW = #{
'ResourceGroupName' = $DWH.ResourceGroupName
'ServerName' = $dwc[0]
'DatabaseName' = $dwc[1]
}
$AzureDWHList += $ThisDW
}
else
{
Write-Warning "Given DataWarehouse '$DWHitem' is not found in given subscription"
}
}
}
else
{
[array]$TotalDataWareHouseList = Get-AzureRmResource | Where-Object ResourceType -EQ "Microsoft.Sql/servers/databases" | Where-Object Kind -Like "*datawarehouse*"
$AzureDWHList = #()
foreach($DWH in $TotalDataWareHouseList)
{
$dwc = $DWH.ResourceName.split("/")
$ThisDW = #{
'ResourceGroupName' = $DWH.ResourceGroupName
'ServerName' = $dwc[0]
'DatabaseName' = $dwc[1]
}
$AzureDWHList += $ThisDW
}
}
<# foreach($AzureDWHItem in $AzureDWHList)
{
if(!(Get-AzureRmResource | ? {$_.Name -eq $AzureDWHItem.ServerName}) )
{
throw " AzureDWH : [$AzureDWHItem] - Does not exist! - please Check your inputs "
}
} #>
if($Action -eq "Suspend")
{
Write-Output "Suspending Azure DataWareHouses";
foreach ($AzureDWH in $AzureDWHList)
{
$status = Get-AzureRmSqlDatabase #AzureDWH | Select Status
if($status.Status -eq "Online")
{
Suspend-AzureRmSqlDatabase #AzureDWH
}
}
}
else
{
Write-Output "Resuming Azure DataWareHouses";
foreach ($AzureDWH in $AzureDWHList)
{
$status = Get-AzureRmSqlDatabase #AzureDWH | Select Status
if($status.Status -eq "Paused")
{
Resume-AzureRmSqlDatabase #AzureDWH
}
}
}
}
catch
{
Write-Error " Exception while getting resource details and writing back to CSV"
Write-Error $_.Exception.message
Write-Error " ErrorStack: $Error[0] "
exit 1
}
}
PauseAzureDWH -AzureSubscriptionId $AzureSubscriptionId -AzureDataWareHouseList $AzureDataWareHouseList -Action $Action

Splitting output of a string into separate strings

I've been working on a powershell script and it's been really boggling my mind. There are 2 parts to the script.
First part is a function that gets all servers in a domain. We have 4 different domains, so I check each one individually and output the result.
Second part is a function that outputs the software on a specific remote machine. In my case, the output from the function above will be seeded into this function to see if a server has a particular piece of software installed.
The function that searches the software works properly. The function that I am getting an output of all the servers is what I am having trouble with.
The issue is, that when I output the list of servers (the output is correct), it outputs everything into a single large multiline string...
For example lets say I have 5 servers: (ServerA, ServerB, ServerC, ServerD, ServerE).
When I run the code I will get an output of all the servers for each domain like so:
TestA.com
ServerA
ServerB
ServerC
ServerD
ServerE
TestB.com
ServerA
ServerB
ServerC
ServerD
ServerE
TestC.com
ServerA
ServerB
ServerC
ServerD
ServerE
TestD.com
ServerA
ServerB
ServerC
ServerD
ServerE
However each domain output is all 1 string, so I can't seed it into the function to check software because it's trying to find it in "ServerA,ServerB,ServerC,ServerD,ServerE", instead of each server individually.
I hope this makes sense. Here is my code to get the list of servers.
#Clear Screen
CLS
function Get-Servers
{
#Variables
[array]$MyDomains="TestA.com","TestB.com","TestC.com","TestD.com"
[array]$MySearchBase="dc=TestA,dc=com","dc=TestB,dc=com","dc=TestC,dc=com","dc=TestD,dc=com"
for($i=0; $i -lt $MyDomains.Count; $i++)
{
Write-Output $($MyDomains[$i])
$MyServers = Get-ADComputer -Filter 'OperatingSystem -like "Windows*Server*"' -Properties Name -SearchBase $($MySearchBase[$i]) -Server $($MyDomains[$i]) | Format-Table Name -HideTableHeaders | out-string
foreach ($MyServer in $MyServers)
{
$MyServer
pause
}
}
}
#Get list of servers
Get-Servers
How can I get the output for each server individually to be stored in the "$MyServer" variable?
EDIT:
Here is my function to find remote software
function Get-RemoteRegistryProgram
{
<#
.Synopsis
Uses remote registry to read installed programs
.DESCRIPTION
Use dot net and the registry key class to query installed programs from a
remote machine
.EXAMPLE
Get-RemoteRegistryProgram -ComputerName Server1
#>
[CmdletBinding()]
Param
(
[Parameter(
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string[]]
$ComputerName = $env:COMPUTERNAME
)
begin
{
$hives = #(
[Microsoft.Win32.RegistryHive]::LocalMachine,
[Microsoft.Win32.RegistryHive]::CurrentUser
)
$nodes = #(
"Software\Microsoft\Windows\CurrentVersion\Uninstall",
"Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
)
}
process
{
$ComputerName
forEach ($computer in $ComputerName)
{
forEach($hive in $hives)
{
try
{
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hive,$computer)
}
catch
{
throw $PsItem
}
forEach($node in $nodes)
{
try
{
$keys = $registry.OpenSubKey($node).GetSubKeyNames()
forEach($key in $keys)
{
$displayname = $registry.OpenSubKey($node).OpenSubKey($key).GetValue('DisplayName')
if($displayname)
{
$installedProgram = #{
# ComputerName = $computer
DisplayName = $displayname
# Version = $registry.OpenSubKey($node).OpenSubKey($key).GetValue('DisplayVersion')
}
New-Object -TypeName PSObject -Property $installedProgram
}
}
}
catch
{
$orginalError = $PsItem
Switch($orginalError.FullyQualifiedErrorId)
{
'InvokeMethodOnNull'
{
#key maynot exists
}
default
{
throw $orginalError
}
}
}
}
}
}
}
end
{
}
}
EDIT 2:
If I modify my server function like so:
for($i=0; $i -lt $MyDomains.Count; $i++)
{
Write-Output $($MyDomains[$i])
$MyServers = Get-ADComputer -Filter 'OperatingSystem -like "Windows*Server*"' -Properties Name -SearchBase $($MySearchBase[$i]) -Server $($MyDomains[$i]) | Format-Table Name -HideTableHeaders
foreach ($MyServer in $MyServers)
{
Get-RemoteRegistryProgram -ComputerName $MyServer
}
}
I get the following error:
Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
Exception calling "OpenRemoteBaseKey" with "2" argument(s): "The network path was not found.
"
At line:47 char:21
+ $registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IOException
Thank you in advance for any help!
Your code is converting the server names to a string
$MyServers = Get-ADComputer -Filter 'OperatingSystem -like "Windows*Server*"' -Properties Name -SearchBase $($MySearchBase[$i]) -Server $($MyDomains[$i]) | Format-Table Name -HideTableHeaders | out-string
The last part of that is out-string. Instead of piping to a format table and pushing it out as a string, keep the objects and use the properties in each object to get the names of each server.
I ended up rewriting some things and fixing my issue. To avoid the string issue, I export the results to a text file and then using get-content I read line by line from the text file and seeded each server to let me know which servers have the software I need. Here is the end result.
#Clear Screen
CLS
function Get-RemoteRegistryProgram
{
<#
.Synopsis
Uses remote registry to read installed programs
.DESCRIPTION
Use dot net and the registry key class to query installed programs from a
remote machine
.EXAMPLE
Get-RemoteRegistryProgram -ComputerName Server1
#>
[CmdletBinding()]
Param
(
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)][string]$ComputerName = $env:COMPUTERNAME,
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=1)][string]$SoftwareName
)
begin
{
$hives = #(
[Microsoft.Win32.RegistryHive]::LocalMachine,
[Microsoft.Win32.RegistryHive]::CurrentUser
)
$nodes = #(
"Software\Microsoft\Windows\CurrentVersion\Uninstall",
"Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
)
}
process
{
$ComputerName
$skip = $false
forEach ($computer in $ComputerName)
{
forEach($hive in $hives)
{
try
{
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hive,$computer)
}
catch
{
$skip = $true
}
if($skip -eq $false)
{
forEach($node in $nodes)
{
try
{
$keys = $registry.OpenSubKey($node).GetSubKeyNames()
forEach($key in $keys)
{
$displayname = $registry.OpenSubKey($node).OpenSubKey($key).GetValue('DisplayName')
#Modified by James
if(($displayname) -like "*$SoftwareName*")
{
$displayname + "`t" + $computer >> c:\scripts\sysaidServers.txt
}
<# Modified by James
if($displayname)
{
$installedProgram = #{
# ComputerName = $computer
DisplayName = $displayname
# Version = $registry.OpenSubKey($node).OpenSubKey($key).GetValue('DisplayVersion')
}
New-Object -TypeName PSObject -Property $installedProgram
}
#>
}
}
catch
{
<#
$orginalError = $PsItem
Switch($orginalError.FullyQualifiedErrorId)
{
'InvokeMethodOnNull'
{
#key maynot exists
}
default
{
throw $orginalError
}
}
#>
}
}
}
}
}
}
end
{
}
}
#Output the servers to a txt file
function Get-Servers
{
param ([Parameter( Mandatory=$true)][string]$SaveFile)
#Variables
[array]$MyDomains="DomainA.com","DomainB.com","DomainC.com","DomainD.com"
[array]$MySearchBase="dc=DomainA,dc=com","dc=DomainB,dc=com","dc=DomainC,dc=com","dc=DomainD,dc=com"
for($i=0; $i -lt $MyDomains.Count; $i++)
{
#I only want servers running Windows Server OS
$MyServers = Get-ADComputer -Filter 'OperatingSystem -like "Windows*Server*"' -Properties Name -SearchBase $($MySearchBase[$i]) -Server $($MyDomains[$i]) | Format-Table Name -HideTableHeaders | out-string
#Remove all whitespace and export to txt file
$MyServers.Trim() -replace (' ', '') >> $SaveFile
}
}
function CheckServerSoftware
{
param ([Parameter( Mandatory=$true)][string]$SaveFile)
Get-Content $SaveFile | ForEach-Object {
if($_ -match $regex)
{
$computer = $_.ToString()
Get-RemoteRegistryProgram -ComputerName $computer.Trim() $SoftwareName
Write-Output ""
}
}
}
#Path to where our exported server list is
$SaveFile = "c:\scripts\servers.txt"
$SoftwareName = "SysAid"
#If the file already exists, remove it
Remove-Item $SaveFile
#Create the text file with servers
Get-Servers $SaveFile
#Import our server list and check software on each server
CheckServerSoftware $SaveFile

Resources