Issue Creating SSL/TLS secure channel - azure

The below given PowerShell Script Basically Checks for Backup Jobs inside Backup Vault and Sends a daily backup job report to emails mentioned. I am Facing issues as the script throws an error
Invoke-RestMethod : The request was aborted: Could not create SSL/TLS secure channel
I am using SendGrid Api Key in a Kv used for the mailing service. Have tried adding an updated TLS Version too it still throws the same error.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Remove-Variable -Name * -ErrorAction SilentlyContinue
$numberofdays = 2
$DAILYBACKUPSTATS = #()
$DAILYBACKUPSTATS1 = #()
$DAILYBACKUPSTATSFAILED = #()
$DAILYBACKUPSTATSFAILED1 = #()
$Subscriptions = Get-AzSubscription
$SubscriptionIDs = $Subscriptions.ID
foreach ($SubscriptionID in $SubscriptionIDs) {
$Subscription = Set-AzContext -SubscriptionId $SubscriptionID
$backupvaults = Get-AzRecoveryServicesVault
$backupvaultnames = $backupvaults.name
foreach ($backupvaultname in $backupvaultnames)
{
$backupvault = Get-AzRecoveryServicesVault -Name $backupvaultname
$startdate = [system.Datetime]::UtcNow
$enddate = ([system.Datetime]::UtcNow).AddDays(1)
for ( $i = 1; $i -le $numberofdays; $i++ ) {
# We query one day at a time
$dailyjoblist = Get-AzRecoveryServicesBackupJob -VaultId $backupvault.ID -From $startdate -To $enddate -Operation Backup;
foreach ( $job in $dailyjoblist ) {
#Extract the information for the reports
$message.Body
$newstatsobj = New-Object System.Object
$newstatsobj | Add-Member -type NoteProperty -name Subscription -value (Get-AzSubscription -SubscriptionId $SubscriptionID).Name
$newstatsobj | Add-Member -type NoteProperty -name Date -value $job.StartTime
$newstatsobj | Add-Member -type NoteProperty -name RecoveryVaultName -value $backupvaultname
$newstatsobj | Add-Member -type NoteProperty -name VMName -value $job.WorkloadName
$newstatsobj | Add-Member -type NoteProperty -name Duration -value $job.Duration
$newstatsobj | Add-Member -type NoteProperty -name Status -value $job.Status
$details = Get-AzRecoveryServicesBackupJob -VaultId $backupvault.ID -Job $job
$DAILYBACKUPSTATS += $newstatsobj
}
$enddate = $enddate.AddDays(-1)
$startdate = $startdate.AddDays(-1)
#Sets the columns
$a = "<style>"
$a = $a + "BODY{background-color:white;}"
$a = $a + "TABLE{border-width: 3px;border-style: double;border-collapse: collapse;font-family: Calibri;}"
$a = $a + "TH{border-width: 3px;padding: 2px;border-style: double;background-color:SandyBrown;font-family: Calibri;}"
$a = $a + "TD{border-width: 3px;padding: 2px;border-style: double;font-family: Calibri;}"
$a = $a + "</style>"
}
}
}
$DAILYBACKUPSTATS += $DAILYBACKUPSTATS1
$EmailBody = "<table width='70%'><tbody>"
$EmailBody += "<tr>"
$EmailBody += "<td width='100%' colSpan=4><font face='Calibri' color='#003399' size='3'>Azure Recovery Services Vault Backup Job Status<BR>NOTE: Date is UTC<BR><BR>"
$EmailBody += "</tr>"
$EmailBody += "</table>"
$message1 = $DAILYBACKUPSTATS | ConvertTo-Html -Head $a
$EmailBody += $message1
$content = $emailBody
$VaultName = "Parent-kv"
$SENDGRID_API_KEY = (Get-AzKeyVaultSecret -VaultName $VaultName -Name "Sendgrd-api").SecretValue
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Bearer " + $SENDGRID_API_KEY)
$headers.Add("Content-Type", "application/json")
$destEmailAddress = "jon#mail.com"
$fromEmailAddress = "Azure-NoReply#cla.com"
$body = #{
personalizations = #(
#{
to = #(
#{
email = $destEmailAddress
}
)
}
)
from = #{
email = $fromEmailAddress
}
subject = $subject
content = #(
#{
type = "text/html"
value = $content
}
)
}
$bodyJson = $body | ConvertTo-Json -Depth 4
$response = Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method Post -Headers $headers -Body $bodyJson

You can most likely search here for a whole bunch of potential remedies.
The request was aborted: Could not create SSL/TLS secure channel
I would also recommend to:
verify that the credentials ($SENDGRID_API_KEY) is correct.
run a network sniffer to see the SSL handshake and see if there any exceptions.

Related

Issue with Automation Accounts>Runbooks: The remote server returned an error: (401) Unauthorized

I am facing on an issue with azure script. I created a runbook in automation accounts and I gave to new app the contributor rights on subscription, but when I run the code I see:
The remote server returned an error: (401) Unauthorized. Invoke-WebRequest : The remote server returned an error: (401) Unauthorized. At line:82 char:13 + $All_BSOD = Invoke-WebRequest -Uri $Top50_BSOD_URL -Method GET -Heade ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand .
At below there are the code:
# Function to send Teams notif
Function Send_Notif
{
param(
$Text,
$Title
)
$Body = #{
'text'= $Text
'Title'= $Title
'themeColor'= "$Color"
}
$Params = #{
Headers = #{'accept'='application/json'}
Body = $Body | ConvertTo-Json
Method = 'Post'
URI = $Webhook_URL
}
Invoke-RestMethod #Params
}
#*****************************************************************
# Part to fill
#*****************************************************************
# Teams webhoot link
$Webhook_URL = "personal link"
# Choose the top x devices (default is 50)
$Top_count = 50
# Teams notif design
$Notif_Title = "Top 50 devices with BSOD"
$Notif_Message = "Here is the list of top 50 devices with BSOD on the last 30 days"
$Color = "2874A6"
#*****************************************************************
# Getting a token
$url = $env:IDENTITY_ENDPOINT
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER)
$headers.Add("Metadata", "True")
$body = #{resource='https://graph.microsoft.com/' }
$script:accessToken = (Invoke-RestMethod $url -Method 'POST' -Headers $headers -ContentType 'application/x-www-form-urlencoded' -Body $body ).access_token
# Authentication
Connect-AzAccount -Identity
$headers = #{'Authorization'="Bearer " + $accessToken}
# Graph URL to use
$Top50_BSOD_URL = "https://graph.microsoft.com/beta/deviceManagement/userExperienceAnalyticsDevicePerformance?dtFilter=all&`$orderBy=blueScreenCount%20desc&`$top=$Top_count&`$filter=blueScreenCount%20ge%201%20and%20blueScreenCount%20le%2050"
$StartupHistory_url = "https://graph.microsoft.com/beta/deviceManagement/userExperienceAnalyticsDeviceStartupHistory?" + '$filter=deviceId%20eq%20%27' + "$DeviceID%27"
# Getting BSOD info
$All_BSOD = Invoke-WebRequest -Uri $Top50_BSOD_URL -Method GET -Headers $Headers -UseBasicParsing
$All_BSOD_JsonResponse = ($All_BSOD.Content | ConvertFrom-Json)
$Get_All_BSOD = $All_BSOD_JsonResponse.value
$BSOD_Array = #()
ForEach($BSOD in $Get_All_BSOD)
{
$Device_Model = $BSOD.model
$Device_Name = $BSOD.deviceName
$BSOD_Count = $BSOD.blueScreenCount
$DeviceID = $BSOD.id
$Get_StartupHistory = Invoke-WebRequest -Uri $StartupHistory_url -Method GET -Headers $Headers -UseBasicParsing
$Get_BSOD_JsonResponse = ($Get_StartupHistory.Content | ConvertFrom-Json)
$Get_BSOD = ($Get_BSOD_JsonResponse.value | Where {$_.restartCategory -eq "blueScreen"})[-1]
$Last_BSOD_Date = [datetime]($Get_BSOD.startTime)
$Last_BSOD_Code = $Get_BSOD.restartStopCode
$OS = $Get_BSOD.operatingSystemVersion
$BSOD_Obj = New-Object PSObject
Add-Member -InputObject $BSOD_Obj -MemberType NoteProperty -Name "Device" -Value $Device_Name
Add-Member -InputObject $BSOD_Obj -MemberType NoteProperty -Name "Model" -Value $Device_Model
Add-Member -InputObject $BSOD_Obj -MemberType NoteProperty -Name "Count" -Value $BSOD_Count
Add-Member -InputObject $BSOD_Obj -MemberType NoteProperty -Name "OS version" -Value $OS
Add-Member -InputObject $BSOD_Obj -MemberType NoteProperty -Name "Last BSOD" -Value $Last_BSOD_Date
Add-Member -InputObject $BSOD_Obj -MemberType NoteProperty -Name "Last code" -Value $Last_BSOD_Code
$BSOD_Array += $BSOD_Obj
}
$BSOD_Table = $BSOD_Array | ConvertTo-HTML -Fragment
$BSOD_Table = $BSOD_Table.Replace("<table>","<table border='1'>")
$Text_Message = "$Notif_Message<br><br>
$BSOD_Table
"
Send_Notif -Text $Text_Message -Title $Notif_Title | out-null
Anybody can help me?
thank you
PS: this is the original webpage text
My aim is to get a notification to all bsod\devices in my enviroment

Add exceptions for file paths from azure defender to adaptive application security controls

I have a bunch of machines being monitored by adaptive application security controls that are giving warnings because the training process was not ran long enough to recognize benign executables. What's an easy way to add exceptions for the executables in active alerts to the adaptive security groups?
There's already an existing recommendation that might provide what you are trying to do:
https://learn.microsoft.com/en-us/azure/defender-for-cloud/adaptive-application-controls#respond-to-the-allowlist-rules-in-your-adaptive-application-control-policy-should-be-updated-recommendation
This script grabs the active alerts from defender, and updates the groups.
The alerts must still be dismissed manually.
function Get-ExistingRules {
Param(
$subscriptionId,
$groupName
)
$url = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Security/locations/centralus/applicationWhitelistings/${groupName}?api-version=2015-06-01-preview";
return az rest `
--method get `
--url $url `
| ConvertFrom-Json;
}
function Add-NewRules {
Param(
$subscriptionId,
$groupName,
$files
)
$url = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Security/locations/centralus/applicationWhitelistings/${groupName}?api-version=2015-06-01-preview";
$existing = Get-ExistingRules $subscriptionId $groupName;
$existing | ConvertTo-Json -Depth 100 > asd.json;
$myList = $existing.properties.pathRecommendations;
foreach ($file in $files) {
$myList += [pscustomobject]#{
path = $file.path
type = "File"
common = $true
action = "Add"
usernames = #(
[pscustomobject]#{
username = "Everyone"
recommendationAction = "Recommended"
}
)
userSids = #(
"S-1-1-0"
)
fileType = $file.type
configurationStatus = "NotConfigured"
};
}
$existing.properties.pathRecommendations = $myList;
$existing.properties = [pscustomobject]#{
protectionMode = $existing.properties.protectionMode
vmRecommendations = $existing.properties.vmRecommendations
pathRecommendations = $existing.properties.pathRecommendations
}
$existing.PSObject.properties.remove("location");
# return $existing;
$body = $existing | ConvertTo-Json -Depth 20 -Compress;
$body > temp.json;
# $body = $body -replace "`"", "\`"";
# return az rest `
# --method PUT `
# --url $url `
# --body $body `
# | ConvertFrom-Json;
# avoid max command length limit by storing body in a file
try {
return az rest `
--method PUT `
--url $url `
--body `#temp.json `
| ConvertFrom-Json;
}
catch {
Write-Warning "Encountered error adding rule";
Write-Warning "$_";
}
return $null;
}
function Format-Body {
param(
$obj
)
$body = $obj | ConvertTo-Json -Depth 100 -Compress;
$body = $body -replace "`"", "\`"";
$body = $body -replace "`r", "";
return $body;
}
Write-Host "Listing subscriptions";
# you can filter to just one subscription if you want
# $subscriptions = az account list --query "[?name=='NPRD'].id" --output tsv;
$subscriptions = az account list --query "[].id" --output tsv;
$allAlerts = New-Object System.Collections.ArrayList;
$i = 0;
foreach ($sub in $subscriptions) {
Write-Progress -Id 0 -Activity "Fetching alerts" -Status $sub -PercentComplete ($i / $subscriptions.Count * 100);
$i = $i++;
$alerts = az security alert list `
--subscription $sub `
| ConvertFrom-Json `
| Where-Object { #("VM_AdaptiveApplicationControlLinuxViolationAudited", "VM_AdaptiveApplicationControlWindowsViolationAudited") -contains $_.alertType } `
| Where-Object { $_.status -eq "Active" };
foreach ($x in $alerts) {
$allAlerts.Add($x) > $null;
}
}
Write-Progress -Id 0 "Done" -Completed;
function Get-Files {
Param(
$alert
)
if ($alert.alertType -eq "VM_AdaptiveApplicationControlLinuxViolationAudited") {
$fileType = "executable";
}
else {
$fileType = "exe";
}
$pattern = "Path: (.*?);";
$str = $alert.extendedProperties.file;
return $str `
| Select-String -Pattern $pattern -AllMatches `
| ForEach-Object { $_.Matches } `
| ForEach-Object { $_.Value } `
| ForEach-Object { [pscustomobject]#{
path = $_
type = $fileType
}};
}
$alertGroups = $allAlerts | Select-Object *, #{Name = "groupName"; Expression = { $_.extendedProperties.groupName } } | Group-Object groupName;
foreach ($group in $alertGroups) {
$groupName = $group.Name;
$group.Group[0].id -match "/subscriptions/([^/]+)/" > $null;
$subscriptionId = $matches[1];
$files = $group.Group | ForEach-Object { Get-Files $_ };
Write-Host "Adding file path rule sub=$subscriptionId group=$groupName count=$($files.Count)";
Add-NewRules $subscriptionId $groupName $files;
}

Azure DB sync logs in log analytics workspace

I have an Azure SQL database sync group that is scheduled to run each hour.
Question:
Can i sent this logs to a log analytics workspace by enabling the diagnostic settings?
If yes, what will be the best way to filter them out?
I can successfully get the logs from powershell, but my end goal here is to create an alert based on the sync logs.
Thanks you in advance!
If you want to send Azure SQL database sync group to a log analytics workspace, you can implement it with HTTP Data Collector API.
For example
$SubscriptionId = "SubscriptionId"
$DS_ResourceGroupName = ""
$DS_ServerName = ""
$DS_DatabaseName = ""
$DS_SyncGroupName = ""
# Replace with your OMS Workspace ID
$CustomerId = "OMSCustomerID"
# Replace with your OMS Primary Key
$SharedKey = "SharedKey"
# Specify the name of the record type that you'll be creating
$LogType = "DataSyncLog"
# Specify a field with the created time for the records
$TimeStampField = "DateValue"
Connect-AzureRmAccount
select-azurermsubscription -SubscriptionId $SubscriptionId
#get log
$endtime =[System.DateTime]::UtcNow
$StartTime = ""
$Logs = Get-AzureRmSqlSyncGroupLog -ResourceGroupName $DS_ResourceGroupName `
-ServerName $DS_ServerName `
-DatabaseName $DS_DatabaseName `
-SyncGroupName $DS_SyncGroupName `
-starttime $StartTime `
-endtime $EndTime;
if ($Logs.Length -gt 0)
{
foreach ($Log in $Logs)
{
$Log | Add-Member -Name "SubscriptionId" -Value $SubscriptionId -MemberType NoteProperty
$Log | Add-Member -Name "ResourceGroupName" -Value $DS_ResourceGroupName -MemberType NoteProperty
$Log | Add-Member -Name "ServerName" -Value $DS_ServerName -MemberType NoteProperty
$Log | Add-Member -Name "HubDatabaseName" -Value $DS_DatabaseName -MemberType NoteProperty
$Log | Add-Member -Name "SyncGroupName" -Value $DS_SyncGroupName -MemberType NoteProperty
#Filter out Successes to Reduce Data Volume to OMS
#Include the 5 commented out line below to enable the filter
#For($i=0; $i -lt $Log.Length; $i++ ) {
# if($Log[$i].LogLevel -eq "Success") {
# $Log[$i] =""
# }
# }
}
$json = ConvertTo-JSON $logs
$result = Post-OMSData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($json)) -logType $logType
if ($result -eq 200)
{
Write-Host "Success"
}
if ($result -ne 200)
{
throw
#"
Posting to OMS Failed
Runbook Name: DataSyncOMSIntegration
"#
}
Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
$xHeaders = "x-ms-date:" + $date
$stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
$bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
$keyBytes = [Convert]::FromBase64String($sharedKey)
$sha256 = New-Object System.Security.Cryptography.HMACSHA256
$sha256.Key = $keyBytes
$calculatedHash = $sha256.ComputeHash($bytesToHash)
$encodedHash = [Convert]::ToBase64String($calculatedHash)
$authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
return $authorization
}
# Create the function to create and post the request
Function Post-OMSData($customerId, $sharedKey, $body, $logType)
{
$method = "POST"
$contentType = "application/json"
$resource = "/api/logs"
$rfc1123date = [DateTime]::UtcNow.ToString("r")
$contentLength = $body.Length
$signature = Build-Signature `
-customerId $customerId `
-sharedKey $sharedKey `
-date $rfc1123date `
-contentLength $contentLength `
-method $method `
-contentType $contentType `
-resource $resource
$uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"
$headers = #{
"Authorization" = $signature;
"Log-Type" = $logType;
"x-ms-date" = $rfc1123date;
"time-generated-field" = $TimeStampField;
}
$response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
return $response.StatusCode
}
After doing that, you can alert vai log search in Azure log analysis. For more detail, please refer to the blog.

How To fix "add-member : Cannot add a member with the name "" because a member with that name already exists"

I need to list multiple FTP connexion on LogicApp service and export this list on csv file. This export is use for see if the SSL protocol connexion is activate or not. My script generate this error.
add-member : Cannot add a member with the name "FTP" because a member with that name already exists. To overwrite the member anyway, add the Force parameter to your command.
LogicApp FTP connexion :
https://learn.microsoft.com/en-us/azure/connectors/connectors-create-api-ftp
I need to add these FTP connections in this format on excel :
format need
$LogicApp_temp = $null
$LogicApp_obj = #()
$LogicApp_temp = new-object PSObject
$FTPAll = Get-AzResource -ResourceGroupName $RG -ResourceType Microsoft.Web/connections
foreach ( $FTPAlls in $FTPAll ) {
$FTPName = $FTPAlls.name
$FTPName1 = Get-AzResource -ResourceGroupName $RG -ResourceType Microsoft.Web/connections -Name $FTPName
foreach ($FTPName2 in $FTPName1) {
$FTPcheckType = $FTPName2.Properties.api.name
if ( $FTPcheckType -eq "ftp") {
$FTPSSL1 = $FTPName1.Properties.parameterValues.isSSL
}
else {
}
$LogicApp_temp | add-member -MemberType NoteProperty -Name "FTP" -Value "$FTPName2.Properties.displayName $FTPSSL1"
}
}
$LogicApp_obj += $LogicApp_temp
Write-Output "LogicApp Name : $Name Ressource Group : $RG OK"
# CSV Exports :
$LogicApp_obj | Export-Csv $csvPath -Append -NoTypeInformation
thank you in advance for your help
Because the PSObject property just could be set one value at one time, if you use the -Force, it will overwrite the value before.
If you want to get the .csv file like the format you provided, we could just append every string like joyftp True first.
My working sample:
$RG = "<resource group name>"
$LogicApp_obj = #()
$value = ""
$FTPAll = Get-AzResource -ResourceGroupName $RG -ResourceType Microsoft.Web/connections
foreach ( $FTPAlls in $FTPAll ) {
$FTPName = $FTPAlls.name
$FTPName1 = Get-AzResource -ResourceGroupName $RG -ResourceType Microsoft.Web/connections -Name $FTPName
foreach ($FTPName2 in $FTPName1) {
$FTPcheckType = $FTPName2.Properties.api.name
$displayname = $FTPName2.Properties.displayName
$LogicApp_temp = New-Object -TypeName PSObject
if ( $FTPcheckType -eq "ftp") {
$FTPSSL1 = $FTPName1.Properties.parameterValues.isSSL
$value += "$displayname $FTPSSL1 | "
$LogicApp_temp | add-member -MemberType NoteProperty -Name "FTP" -Value "$value" -Force
}
}
}
Write-Output "LogicApp Name : $Name Ressource Group : $RG OK"
# CSV Exports :
$LogicApp_temp | Export-Csv -Path "C:\Users\joyw\Desktop\ftp.csv" -NoTypeInformation
The .csv file:

Azure VM OS Build - Powershell

I am trying to create a powershell command to loop through all my Azure subscriptions and get the OS build number of the VMs
param(
# Specify the location of the audit file
$csvFilePath = "C:\agentAudit.csv"
)
cls
$ErrorActionPreference = 'Stop'
Write-Host "Validating Azure Accounts..."
try{
$subscriptionList = Get-AzureRmSubscription | Sort SubscriptionName
}
catch {
Write-Host "Reauthenticating..."
Login-AzureRmAccount | Out-Null
$subscriptionList = Get-AzureRmSubscription | Sort SubscriptionName
}
if (Test-Path $csvFilePath) {
Remove-Item -Path $csvFilePath
}
foreach($subscription in $subscriptionList) {
Select-AzureRmSubscription -SubscriptionId $subscription.SubscriptionId | Out-Null
Write-Output "`n Working on subscription: $($subscription.SubscriptionName) `n"
$vms = Get-AzureRmVM -WarningAction Ignore
foreach ($vm in $vms) {
$VMs = Get-AzureRmVM
$vmlist = #()
$VMs | ForEach-Object {
$VMObj = New-Object -TypeName PSObject
$VMObj | Add-Member -MemberType Noteproperty -Name "VM Name" -Value $_.Name
$VMObj | Add-Member -MemberType Noteproperty -Name "OS type" -Value $_.StorageProfile.ImageReference.Sku
$VMObj | Add-Member -MemberType Noteproperty -Name "OS Offer" -Value $_.StorageProfile.ImageReference.Offer
$VMObj | Add-Member -MemberType Noteproperty -Name "OS Publisher" -Value $_.StorageProfile.ImageReference.Publisher
$VMObj | Add-Member -MemberType Noteproperty -Name "OS Version" -Value $_.StorageProfile.ImageReference.Version
$vmlist += $VMObj
}
$vmlist
}
}
I am pretty new to to Powershell and still learning to understand and write PS
Long time back i created a script for generating the Azure inventory. May be this can help you. You just need to tweak it a bit
Add-AzureRmAccount
$SubscriptionNames = "Prototypes","Development"
foreach($SubscriptionName in $SubscriptionNames)
{
try
{
Select-AzureRmSubscription -SubscriptionName $SubscriptionName
$VMinventory = Get-AzurermVM -ErrorAction Stop
$OutputPath = "C:\VMInventoryRM-" + $SubscriptionName + ".csv"
$report=#()
foreach ($vm in $VMinventory)
{
$VMinfo = " " | select Powerstate, Location, VMSize
# Display
write-host($vm.Name)
$vmStatus = $null
$Location = $null
$VMSize = $null
$vmStatus = (get-azurermvm -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Status).Statuses[1].Code
$Location = $vm.Location
$VMSize = $vm.HardwareProfile.VmSize
$VMinfo.Powerstate = $vmStatus
$VMinfo.Location = $Location
$VMinfo.VMSize = $VMSize
$Report += $VMinfo
$i++
}
$Report | export-csv $OutputPath -NoTypeInformation
}
catch
{}
}
I found the answer in the below technet article
function Get-WindowsVersion {
<#
.SYNOPSIS
List Windows Version from computer. Compatible with PSVersion 3 or higher.
.DESCRIPTION
List Windows Version from computer. Compatible with PSVersion 3 or higher.
.PARAMETER ComputerName
Name of server to list Windows Version from remote computer.
.PARAMETER SearchBase
AD-SearchBase of server to list Windows Version from remote computer.
.PARAMETER History
List History Windows Version from computer.
.PARAMETER Force
Disable the built-in Format-Table and Sort-Object.
.NOTES
Name: Get-WindowsVersion.psm1
Author: Johannes Sebald
Version: 1.2.5
DateCreated: 2016-09-13
DateEdit: 2018-07-11
.LINK
https://www.dertechblog.de
.EXAMPLE
Get-WindowsVersion
List Windows Version on local computer with built-in Format-Table and Sort-Object.
.EXAMPLE
Get-WindowsVersion -ComputerName pc1
List Windows Version on remote computer with built-in Format-Table and Sort-Object.
.EXAMPLE
Get-WindowsVersion -ComputerName pc1,pc2
List Windows Version on multiple remote computer with built-in Format-Table and Sort-Object.
.EXAMPLE
Get-WindowsVersion -SearchBase "OU=Computers,DC=comodo,DC=com" with built-in Format-Table and Sort-Object.
List Windows Version on Active Directory SearchBase computer.
.EXAMPLE
Get-WindowsVersion -ComputerName pc1,pc2 -Force
List Windows Version on multiple remote computer and disable the built-in Format-Table and Sort-Object.
.EXAMPLE
Get-WindowsVersion -History with built-in Format-Table and Sort-Object.
List History Windows Version on local computer.
.EXAMPLE
Get-WindowsVersion -ComputerName pc1,pc2 -History
List History Windows Version on multiple remote computer with built-in Format-Table and Sort-Object.
.EXAMPLE
Get-WindowsVersion -ComputerName pc1,pc2 -History -Force
List History Windows Version on multiple remote computer and disable built-in Format-Table and Sort-Object.
#>
[cmdletbinding()]
param (
[parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string[]]$ComputerName = "localhost",
[string]$SearchBase,
[switch]$History,
[switch]$Force
)
if ($($PsVersionTable.PSVersion.Major) -gt "2") {
# SearchBase
if ($SearchBase) {
if (Get-Command Get-AD* -ErrorAction SilentlyContinue) {
if (Get-ADOrganizationalUnit -Filter "distinguishedName -eq '$SearchBase'" -ErrorAction SilentlyContinue) {
$Table = Get-ADComputer -SearchBase $SearchBase -Filter *
$ComputerName = $Table.Name
}
else {Write-Warning "No SearchBase found"}
}
else {Write-Warning "No AD Cmdlet found"}
}
# Loop 1
$Tmp = New-TemporaryFile
foreach ($Computer in $ComputerName) {
if (Test-Connection -ComputerName $Computer -Count 1 -ErrorAction SilentlyContinue) {
try {
$WmiObj = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer
}
catch {
Write-Warning "$Computer no wmi access"
}
if ($WmiObj) {
# Variables
$WmiClass = [WmiClass]"\\$Computer\root\default:stdRegProv"
$HKLM = 2147483650
$Reg1 = "SOFTWARE\Microsoft\Windows NT\CurrentVersion"
$Reg2 = "SYSTEM\Setup"
if ($History) {$KeyArr = ($WmiClass.EnumKey($HKLM, $Reg2)).snames -like "Source*"} else {$KeyArr = $Reg1}
# Loop 2
foreach ($Key in $KeyArr) {
if ($History) {$Reg = "$Reg2\$Key"} else {$Reg = $Key}
$Major = $WmiClass.GetDWordValue($HKLM, $Reg, "CurrentMajorVersionNumber").UValue
$Minor = $WmiClass.GetDWordValue($HKLM, $Reg, "CurrentMinorVersionNumber").UValue
$Build = $WmiClass.GetStringValue($HKLM, $Reg, "CurrentBuildNumber").sValue
$UBR = $WmiClass.GetDWordValue($HKLM, $Reg, "UBR").UValue
$ReleaseId = $WmiClass.GetStringValue($HKLM, $Reg, "ReleaseId").sValue
$ProductName = $WmiClass.GetStringValue($HKLM, $Reg, "ProductName").sValue
$ProductId = $WmiClass.GetStringValue($HKLM, $Reg, "ProductId").sValue
$InstallTime1 = $WmiClass.GetQWordValue($HKLM, $Reg, "InstallTime").UValue
$InstallTime2 = ([datetime]::FromFileTime($InstallTime1))
# Variables Windows 6.x
if ($Major.Length -le 0) {$Major = $WmiClass.GetStringValue($HKLM, $Reg, "CurrentVersion").sValue}
if ($ReleaseId.Length -le 0) {$ReleaseId = $WmiClass.GetStringValue($HKLM, $Reg, "CSDVersion").sValue}
if ($InstallTime1.Length -le 0) {$InstallTime2 = ([WMI]"").ConvertToDateTime($WmiObj.InstallDate)}
# Add Points
if (-not($Major.Length -le 0)) {$Major = "$Major."}
if (-not($Minor.Length -le 0)) {$Minor = "$Minor."}
if (-not($UBR.Length -le 0)) {$UBR = ".$UBR"}
# Output
$Output = New-Object -TypeName PSobject
$Output | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer.toUpper()
$Output | Add-Member -MemberType NoteProperty -Name ProductName -Value $ProductName
$Output | Add-Member -MemberType NoteProperty -Name WindowsVersion -Value $ReleaseId
$Output | Add-Member -MemberType NoteProperty -Name WindowsBuild -Value "$Major$Minor$Build$UBR"
$Output | Add-Member -MemberType NoteProperty -Name ProductId -Value $ProductId
$Output | Add-Member -MemberType NoteProperty -Name InstallTime -Value $InstallTime2
$Output | Export-Csv -Path $Tmp -Append
}
}
}
else {Write-Warning "$Computer not reachable"}
}
# Output
if ($Force) {Import-Csv -Path $Tmp} else {Import-Csv -Path $Tmp | Sort-Object -Property ComputerName, WindowsVersion | Format-Table -AutoSize}
}
else {Write-Warning "PSVersion to low"}
}
https://gallery.technet.microsoft.com/scriptcenter/Get-WindowsVersion-can-0726c5d4#
Thank You Johannes Sebald

Resources