Exclude list of Vms from script deleting snapshots in vmware - powercli

I have a powercli script which is scheduled to delete the VMs older than X days, recently we got a list of VMs which are supposed to be excluded from the snapshot deletion as these are critical Snapshots.
How do I introduce the parameter exclude VM in my script to compare the VMS with a snapshot in Vcenter to the list I provide and list and delete only VMS which meets the criteria of not older than X days and not part of the Exclude VM list.
I am relatively new and using below code to fetch snapshots older than 10 Days and delete them.
# vCenter Server configuration
$vcenter = "Vcenter Name"
$vcenteruser = "Domain\Userid"
$vcenterpw = "Password"
#Connect to the vCenter server defined above. Ignore certificate errors
Connect to vcenter Server connect-viserver $vcenter -User $vcenteruser -Password $vcenterpw"
Add-PSSnapin VMware.VimAutomation.Core -ErrorAction 'SilentlyContinue'
Clear-Host
$old_snapshots = Get-VM | Get-Snapshot |? { ([DateTime]::Now - $_.Created).TotalDays -gt 7 } | Remove-Snapshot: $old_snapshots | Remove-Snapshot -RunAsync -Confirm:$false
I need to figure out what if i have a list of 'vms' having snapshots older than 10 days which shouldn't be deleted. I want to exclude those 'Vms' but i am not sure how to do that.
So I tried Using the logic by # I.T Delinquent . I put a value in '$VmToIgnore' and compare it with the list of VMs received in 'Get-Vm'. If it is true do nothing, If it s false, get snapshot and other attribute of that VM and export it to CSV.
$vmsToIgnore ="Vm1"
$e = Get-VM
Foreach-Object {
if ($vmsToIgnore -Contains $_.Name){
#Do nothing as VM name is in the vmsToIgnore list
}else{
$f = $e |get-Snapshot| Select-object
vm,VMId,name,Description,SizeGB,created
$f| Export-Csv -Path "\\%path%\snapshot.csv"
}
}
This is still returning list of all 'VM' Snapshots including the one n '$VMToIgnore'.
I must be making some mistake here as it should not print the '$VmToIgnore' in Excel.

Could you build upon something like this:
$vmsToIgnore = "VM1","VM2"
$old_Snapshots = Get-VM | Foreach-Object {
if ($vmsToIgnore -Contains $_.Name){
#Do nothing as VM name is in the vmsToIgnore list
}else{
#Perform tasks as vm isn't in vmsToIgnore list
}
}
I'm not really sure what Get-VM will return so please you will likely have to edit my example. Here is the basis of it though assuming that Get-VM returns a nice list of VMs:
$vmsToIgnore = "VM1","VM2"
$outputFromGetVM = "VM1","VM1.2","VM2","VM3"
foreach ($vm in $outputFromGetVM){
if ($vmsToIgnore -contains $vm){
Write-Host "Ignoring"
}else{
Write-Host "performing"
}
}
Let me know if you have any questions
UPDATE
I think this could be used:
$vmsToIgnore = "Vm1"
Get-VM | ForEach-Object {
if ($vmsToIgnore -contains $_.Name){
#Do nothing as VM name is in the vmsToIgnore list
}else{
Get-Snapshot $_ | Select-Object vm, VmId, Name, Description, SizeGB, Created | Export-Csv -Path "\\path\to\file.csv"
}
}

Sounds like you have the VM names as a list already in which case exclude them
$list = Get-VM | where {"name" -ne "mypreciousvm"}
You will need to play with -notmatch or -notcontains I can't remember which one you will need. Then pass the exclusion list below.
$old_snapshots = Get-VM -name $list | Get-Snapshot |? { ([DateTime]::Now - $_.Created).TotalDays -gt 7 } | Remove-Snapshot: $old_snapshots | Remove-Snapshot -RunAsync -Confirm:$false
If Vms come and go a lot then try to do it in one go otherwise you may get an error as you reget the vm name
$old_snapshots = Get-VM | where {$.name -notcontains $list}| Get-Snapshot |? { ([DateTime]::Now - $.Created).TotalDays -gt 7 } | Remove-Snapshot: $old_snapshots | Remove-Snapshot -RunAsync -Confirm:$false

Delete Snapshots that are older than 3 days
Get-VM | Get-Snapshot | ? {$_.created -lt (Get-Date).AddDays(-3)} | Remove-Snapshot -Confirm:$false -RunAsync

Related

PowerShell 7. ForEach-Object -Parallel Does Not Autheticate Against Azure PowerShell

We wrote a script that supposed to execute Azure PowerShell commands in parallel. The problem is when we increase -ThrottleLimit higher than one, some of the commands are not being performed properly. The script is:
# Writing IPs for whitelisting into file.
Add-Content -Path IPs.txt -Value ((Get-AzWebApp -ResourceGroupName "ResourceGroup1" -Name "WebApp1").OutboundIpAddresses).Split(",")
Add-Content -Path IPs.txt -Value ((Get-AzWebApp -ResourceGroupName "ResourceGroup1" -Name "WebApp1").PossibleOutboundIpAddresses).Split(",")
# Writing new file with inique IPs.
Get-Content IPs.txt | Sort-Object -Unique | Set-Content UniqueIPs.txt
# Referencing the file.
$IPsForWhitelisting = Get-Content UniqueIPs.txt
# Assigning priotiry number to each IP
$Count = 100
$List = foreach ($IP in $IPsForWhitelisting) {
$IP|Select #{l='IP';e={$_}},#{l='Count';e={$Count}}
$Count++
}
# Whitelisting all the IPs from the list.
$List | ForEach-Object -Parallel {
$IP = $_.IP
$Priority = $_.Count
$azureApplicationId ="***"
$azureTenantId= "***"
$azureApplicationSecret = "***"
$azureSecurePassword = ConvertTo-SecureString $azureApplicationSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($azureApplicationId , $azureSecurePassword)
Connect-AzAccount -Credential $credential -TenantId $azureTenantId -ServicePrincipal | Out-null
echo "IP-$Priority"
echo "$IP/24"
echo $Priority
Add-AzWebAppAccessRestrictionRule -ResourceGroupName "ResourceGroup1" -WebAppName "WebApp1" -Name "IP-$Priority" -Priority $Priority -Action Allow -IpAddress "$IP/24"
} -ThrottleLimit 1
If ThrottleLimit is set to 1 - 8 rules are being created, if ThrottleLimit is set to 2 - 7 rules are being created, 3 - 4 rules, 10 - 1 rule, hence some rules are being skipped.
What is the reason for such behavior?
In short - the -Parallel parameter does not (yet perhaps) magically import all dependent variables that fall in the scope of the For-EachObject block. In reality PWSH spans separate processes and only the array that is looped over will be implicitly passed, all other variables need explicit designations.
One should use the $using: directive (prefix) to denote which variables are to be imported (made visible) in the parallel code block.
Example:
$avar = [Int]10
$bvar = [Int]20
$list = #('here', 'it', 'eees')
$list | ForEach-Object -Parallel {
Write-Output "(a, b) is here ($($using:avar), $($using:bvar))"
Write-Output "(a, b) missing ($($avar), $($bvar))"
Write-Output "Current element is $_"
}```
*thus - the described behavior is likely due to the fact that config. variables are not imported (at all) and thus the operations silently fail.*

Power shell Script to Enable Server level and databases triggers

What is the best way to enable all server level and DB level triggers for multiple server?
Thanks
Using Powershell SQLSERVER module:
-- Run below command to install sqlserver power shell module
# install-module sqlserver
Import-Module sqlserver
# getting the servers list
$servers = Get-Content C:\sql\servers.txt
Foreach($server in $servers)
{
cd sqlserver:/sql/$server
# checking if a server has more than one instance
IF ((dir).count -gt 1)
{
cd sqlserver:\sql\$server
dir | %{ $instname = $_.displayname
"***************************************$server $instname*************************************************"
"Enabling Server Level Triggers on: $server\$instname"
cd sqlserver:\sql\$server\$instname\triggers
dir | %{$_.refresh()}
# enabling server level triggers
dir | ?{$_.isenabled -eq $false} | %{$_.isenabled = $true ; $_.alter() ; $_.refresh()}
cd sqlserver:\sql\$server\$instname\databases
# looping through the databases on each instance and enabling the triggers
dir | %{ $DB = $_.name
cd sqlserver:\sql\$server\$instname\databases\$DB\Triggers
"Enabling Triggers on: $DB"
dir | %{$_.refresh()}
#$server
dir | ?{$_.isenabled -eq $false} | %{$_.isenabled = $true ; $_.alter() ; $_.refresh()}
}
}
}
else # for servers with default instance
{
cd sqlserver:\sql\$server\default
cd sqlserver:\sql\$server\default\triggers
"***************************************$server*************************************************"
"Enabling Server Level Triggers on: $server\Default"
dir | %{$_.refresh()}
# enabling server level triggers
dir | ?{$_.isenabled -eq $false} | %{$_.isenabled = $true ; $_.alter() ; $_.refresh()}
cd sqlserver:\sql\$server\default\databases
# looping through the databases on each instance and enabling the triggers
dir | %{ $DB = $_.name
cd sqlserver:\sql\$server\default\databases\$DB\Triggers
"Enabling Triggers on: $DB"
dir | %{$_.refresh()}
#$server
dir | ?{$_.isenabled -eq $false} | %{$_.isenabled = $true ; $_.alter() ; $_.refresh()}
}
}
}
Write-Host "Done....."
cd c:\
Start-Sleep -s 10

Invoke-AzVMRunCommand as a job

I am trying to use Invoke-AzVMRunCommand as a job. when I executed below script the job is created and executed successfully but I am failing to write the output like which job result belongs to which vm.
Invoke-AzVMRunCommand is used to invoke a command on a particular VM. You should have this information beforehand.
Here is some information on -AsJob parameter
https://learn.microsoft.com/en-us/powershell/module/az.compute/invoke-azvmruncommand?view=azps-2.6.0#parameters
As suggested by AmanGarg-MSFT, you should have that information before hand. You can use a hashtable $Jobs to store the server name and Invoke-AzVMRunCommand output and later iterate through using the $Jobs.GetEnumerator().
$Jobs = #{}
$Servers = "Server01","Server02"
[System.String]$ScriptBlock = {Get-Process}
$FileName = "RunScript.ps1"
Out-File -FilePath $FileName -InputObject $ScriptBlock -NoNewline
$Servers | ForEach-Object {
$vm = Get-AzVM -Name $_
$Jobs.Add($_,(Invoke-AzVMRunCommand -ResourceGroupName $vm.ResourceGroupName -Name $_ -CommandId 'RunPowerShellScript' -ScriptPath $FileName -AsJob))
}

Variable won't store data in powershell

For some reason I can't get this variable ($SelectedDrive) to store data in it using the Get-Content cmdlet.
I have created a function as shown below:
#Allow user to select which drive to backup to and checks that the user hasn't canceled out of the Drive Selection GUI
Function SelectDrive{
ShowUSBs | Out-File $locBackupFolder\RemovableStorage.txt
Get-Content $locBackupFolder\RemovableStorage.txt | Out-GridView -OutputMode Single -Title "Choose USB Storage Device" | Out-File $locBackupFolder\Drive.txt -Encoding default
$SelectedDrive = Get-Content $locBackupFolder\Drive.txt
IF ($SelectedDrive -eq $null) {
WarningCancel
RemoveBackupDrive
Exit
} ELSE {
$BackupDrive = $SelectedDrive.Substring(0,2)
}
}
When I run the script in Powershell ISE using the F5 button it runs through, and then fails to enter the data.
I go back and check it over and can't see any issue, as it works if I run it piece by piece using Run Selection.
The whole script is below:
######### DEFINE FUNCTIONS #########
$locBackupFolder = "C:\Backup"
#Check Backup Drive
IF (!(Test-Path $locBackupFolder)) {
#C:\Backup does NOT exist
New-Item $locBackupFolder -ItemType Directory
}
#Inform user only D:\Username will be backed up
function WarningDialog(
$MessageWarning = "!!!WARNING!!!
YOU MAY LOSE DATA IF YOU DO NOT ADHERE TO THIS MESSAGE
Only D:\$env:USERNAME will be backed up, please ensure any Data that is sitting in the root of D:\ is moved to D:\$env:USERNAME
FYI: Your desktop data is safe and will be backed up, you need only worry about data in the root of D:\ or anything you have stored in
C:\ Drive.
FURTHER MORE Outlook will shutdown by itself through this process, please do not open it up again until everything is finished.
If you have data to move, please click Cancel now, otherwise please press OK to continue the backup procedure.
For any help, please see your IT Technicians, or call
off-site.",
$WindowTitleWarning = "Backup your data from your laptop",
[System.Windows.Forms.MessageBoxButtons]$ButtonsWarning = [System.Windows.Forms.MessageBoxButtons]::OKCancel,
[System.Windows.Forms.MessageBoxIcon]$IconWarning = [System.Windows.Forms.MessageBoxIcon]::Stop
)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($MessageWarning, $WindowTitleWarning, $ButtonsWarning, $IconWarning)
}
#Checks to see if logged on user has a D:\USERNAME directory and if not, informs them to see IT for a custom backup.
Function TestUserDrive {
IF (!(Test-Path "D:\$env:USERNAME")){
#D:\USERNAME does NOT exist
}
}
#Displays an instruction/how-to of how to move data from the root of D:\ to the users D:\USERNAME folder
function WarningCancel(
$MessageCancel = "INSTRUCTIONS:
You have chosen to cancel the backup script, if this is due to requiring data to be moved to inside your D:\$env:USERNAME folder, please do the following.
1. Open My Computer
2. Double click on Data (D:) to open your D:\ Drive
3. Move or Copy anything from this directory that you wish to keep, into the directory called $env:USERNAME\My Documents
4. Once this has been completed, re-run this script to commence the backup procedure again
Easy as that!
For any help, please see your IT Technicians, or call
off-site",
$WindowTitleCancel = "Backup your data from your laptop",
[System.Windows.Forms.MessageBoxButtons]$ButtonsCancel = [System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]$IconCancel = [System.Windows.Forms.MessageBoxIcon]::Information
)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($MessageCancel, $WindowTitleCancel, $ButtonsCancel, $IconCancel)
}
#Informs the user to select the device they would like to backup to when the selection box is displayed
function SelectDevicePrompt(
$MessageSelect = "On the next screen please specify the device you would like to backup your data to.
The devices you currently have plugged in will show, please select your chosen device, and then click the OK button at the bottom right of the window.",
$WindowTitleSelect = "Backup your data from your laptop",
[System.Windows.Forms.MessageBoxButtons]$ButtonsSelect = [System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]$IconSelect = [System.Windows.Forms.MessageBoxIcon]::Hand
)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($MessageSelect, $WindowTitleSelect, $ButtonsSelect, $IconSelect)
}
#Displays a list of all removable storage devices volume names and their allocated drive letter
Function ShowUSBs{
$USBInfo = gwmi win32_diskdrive | ?{$_.interfacetype -eq "USB"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"}
$USBInfo | Format-Table `
#{Expression={$_.DeviceID};Label="Drive Letter";Width=25}, `
#{Expression={$_.VolumeName};Label="USB Name";Width=20}
}
#Allow user to select which drive to backup to and checks that the user hasn't canceled out of the Drive Selection GUI
Function SelectDrive{
ShowUSBs | Out-File $locBackupFolder\RemovableStorage.txt
Get-Content $locBackupFolder\RemovableStorage.txt | Out-GridView -OutputMode Single -Title "Choose USB Storage Device" | Out-File $locBackupFolder\Drive.txt -Encoding default
$SelectedDrive = Get-Content $locBackupFolder\Drive.txt
IF ($SelectedDrive -eq $null) {
WarningCancel
RemoveBackupDrive
Exit
} ELSE {
$BackupDrive = $SelectedDrive.Substring(0,2)
}
}
$BackupDrive
#Imports list of active processes and looks for outlook process then kills it if found
function KillOutlook{
$processactive = Get-Process
IF($processactive.ProcessName -contains "Outlook") {
Stop-Process -Name Outlook
Start-Sleep 1
$OLcheckagain = Get-Process
IF($OLcheckagain.Processname -contains "Outlook") {
Write-Host "Outlook failed to close"
}
} Else {
Write-Host "Outlook is closed"
}
}
#Find the pst files attached to outlook and output the values to C:\Backup\PST.txt
function FindPSTs{
$outlook = New-Object -comObject Outlook.Application
$pstloc = $outlook.Session.Stores | where { ($_.FilePath -like '*.PST') }
$pstloc.FilePath | out-file -FilePath "$locBackupFolder\PST.txt" -Encoding Default
}
#Removes C:\Backup Directory
Function RemoveBackupDrive {
IF (Test-Path $locBackupFolder){
Remove-Item $locBackupFolder -Force -Recurse
}
}
#Copy data from D:\USERNAME to BackupDrive
Function CopyData {
IF (!(Test-Path $BackupDrive)) {
robocopy D:\$env:USERNAME $BackupDrive /MIR /COPYALL /r:03 /w:5 /MT:9
} ELSE {
robocopy D:\$env:USERNAME $BackupDrive /
}
}
#Copy PST files explicitly to BackupDrive\AppData\Roaming\Email
Function CopyPST {
KillOutlook
Start-Sleep 1
IF (!(Test-Path $BackupDrive\AppData\Roaming\Email)) {
New-Item $BackupDrive\AppData\Roaming\Email -ItemType Directory
}
Get-Content $locBackupFolder\PST.txt | ForEach-Object {
IF (Test-Path $_){
Copy-Item $_ "$BackupDrive\AppData\Roaming\Email"
}
}
}
######### START SCRIPT #########
#Display warning to inform user that only D:\USERNAME will be backed up
$WarningAccept = WarningDialog
#If cancel is selected from WarningDialog, then display WarningCancel message pop-up giving instructions to backup data from D:\ to D:\USERNAME
IF ($WarningAccept -eq "Cancel") {
WarningCancel
RemoveBackupDrive
Exit
}
#Prompts user to select Device to backup to
SelectDevicePrompt
#Shows the selection page for the user to select the device
SelectDrive
#Find the pst files attached to outlook and output to C:\Backup\PST.txt
FindPSTs
#Inform user where their data will be backed up to
Write-Host "Your data will be backed up to $BackupDrive"
#If Outlook is found, stop its process, otherwise continue
KillOutlook
#Running backup of everything in D:\ drive using robocopy
#If this is the first time copying all data /MIR will be used, otherwise if the directory DRIVE:\USERNAME\Backup exists
#robocopy will not /MIR and will only update newly added data.
CopyData
#Copy PST files specifically
#CopyPST
If I can be of any further assistance, please let me know.
I think your problem is simply that the SelectDrive function doesn't return anything. $backupDrive is local to the function, so you cannot refer to it from outside the function.
Also converting everything to text is going to introduce other bugs, e.g. you can select any line from the grid view including your headers and the blank lines top and bottom. Some better code might be:
Function GetUSBs{
$USBInfo = gwmi win32_diskdrive |
?{$_.interfacetype -eq "USB"} |
%{gwmi -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"} |
%{gwmi -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"}
Write-Output $USBInfo | select #{n="Drive Letter";e={$_.DeviceID}},#{n="USB Name";e={$_.VolumeName}}
}
Function SelectDrive{
$usbs = GetUSBs
$usbs | Format-Table -AutoSize | Out-File $locBackupFolder\RemovableStorage.txt
$selectedDrive = $usbs | Out-GridView -OutputMode Single -Title "Choose USB Storage Device"
$selectedDrive | Out-File $locBackupFolder\Drive.txt -Encoding default
if ($SelectedDrive -eq $null) {
WarningCancel
RemoveBackupDrive
exit
} else {
write-output $selectedDrive.'Drive Letter'.Substring(0,2)
}
}
$backupDrive = SelectDrive
This version still writes to your two files, but it doesn't read either of them, instead it keeps the objects internally. This means you get a better display in the grid view as the grid's own column headings are used, and you don't get any selectable lines you don't want.
There's a lot going on in your script, I debugged through it myself and found that these lines are causing the problems
ShowUSBs | Out-File $locBackupFolder\RemovableStorage.txt
Get-Content $locBackupFolder\RemovableStorage.txt | Out-GridView -OutputMode Single -Title "Choose USB Storage Device" | Out-File $locBackupFolder\Drive.txt -Encoding default
$SelectedDrive = Get-Content "$locBackupFolder\Drive.txt"
You need to remove this block of code:
Out-File $locBackupFolder\Drive.txt -Encoding default
It is creating a new blank Drive.txt file, after which, Get-Content is being called on that empty file.
Therefore, it is getting the content of a blank file, which will always result in the $SelectedDrive variable being set to null.
I've added content to my Drive.txt file after this block has been removed and the $SelectedDrive variable is set to the content of the file as expected
This construct won't work as you seem to expect:
Get-Content file | Out-GridView | Out-File otherfile
Out-GridView doesn't pass the data back into the pipeline for further processing. If you need data in both a grid view and a file you need to use the Tee-Object cmdlet before Out-GridView:
Get-Content file | Tee-Object otherfile | Out-GridView
In your case:
Get-Content "$locBackupFolder\RemovableStorage.txt" |
Tee-Object "$locBackupFolder\Drive.txt" |
Out-GridView -OutputMode Single -Title 'Choose USB Storage Device'
If you want the user to select a particular Item, Out-GridView won't work at all, because it's for output only (hence the verb Out). You'll need a form with a list box to allow the user to select an item:
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$frm = New-Object Windows.Forms.Form
$btn = New-Object Windows.Forms.Button
$btn.Location = New-Object Drawing.Size(10,220)
$btn.Size = New-Object Drawing.Size(75,23)
$btn.Text = "OK"
$btn.Add_Click({$frm.Close()})
$frm.Controls.Add($btn)
$list = New-Object Windows.Forms.ListBox
$list.Location = New-Object Drawing.Size(10,10)
$list.Size = New-Object Drawing.Size(260,40)
$list.Height = 200
Get-Content "$locBackupFolder\RemovableStorage.txt" | % {
$list.Items.Add($_)
} | Out-Null
$frm.Controls.Add($list)
$frm.Add_Shown({$frm.Activate()})
[void]$frm.ShowDialog()
$SelectedDrive = $list.SelectedItem

How do I check for the SQL Server Version using Powershell?

What's the easiest way to check for the SQL Server Edition and Version using powershell?
Just an option using the registry, I have found it can be quicker on some of my systems:
$inst = (get-itemproperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server').InstalledInstances
foreach ($i in $inst)
{
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i
(Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").Edition
(Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").Version
}
Invoke-Sqlcmd -Query "SELECT ##VERSION;" -QueryTimeout 3
http://msdn.microsoft.com/en-us/library/cc281847.aspx
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
$srv = New-Object "Microsoft.SqlServer.Management.Smo.Server" "."
$srv.Version
$srv.EngineEdition
Obviously, replace "." with the name of your instance. If you want to see all the methods available, go here.
Hacked up advice from this thread (and some others), this went in my psprofile:
Function Get-SQLSvrVer {
<#
.SYNOPSIS
Checks remote registry for SQL Server Edition and Version.
.DESCRIPTION
Checks remote registry for SQL Server Edition and Version.
.PARAMETER ComputerName
The remote computer your boss is asking about.
.EXAMPLE
PS C:\> Get-SQLSvrVer -ComputerName mymssqlsvr
.EXAMPLE
PS C:\> $list = cat .\sqlsvrs.txt
PS C:\> $list | % { Get-SQLSvrVer $_ | select ServerName,Edition }
.INPUTS
System.String,System.Int32
.OUTPUTS
System.Management.Automation.PSCustomObject
.NOTES
Only sissies need notes...
.LINK
about_functions_advanced
#>
[CmdletBinding()]
param(
# a computer name
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String]
$ComputerName
)
# Test to see if the remote is up
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet) {
# create an empty psobject (hashtable)
$SqlVer = New-Object PSObject
# add the remote server name to the psobj
$SqlVer | Add-Member -MemberType NoteProperty -Name ServerName -Value $ComputerName
# set key path for reg data
$key = "SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL"
# i have no idea what this does, honestly, i stole it...
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
# set up a .net call, uses the .net thingy above as a reference, could have just put
# 'LocalMachine' here instead of the $type var (but this looks fancier :D )
$regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $ComputerName)
# make the call
$SqlKey = $regKey.OpenSubKey($key)
# parse each value in the reg_multi InstalledInstances
Foreach($instance in $SqlKey.GetValueNames()){
$instName = $SqlKey.GetValue("$instance") # read the instance name
$instKey = $regKey.OpenSubkey("SOFTWARE\Microsoft\Microsoft SQL Server\$instName\Setup") # sub in instance name
# add stuff to the psobj
$SqlVer | Add-Member -MemberType NoteProperty -Name Edition -Value $instKey.GetValue("Edition") -Force # read Ed value
$SqlVer | Add-Member -MemberType NoteProperty -Name Version -Value $instKey.GetValue("Version") -Force # read Ver value
# return an object, useful for many things
$SqlVer
}
} else { Write-Host "Server $ComputerName unavailable..." } # if the connection test fails
}
To add to Brendan's code.. this fails if your machine is 64-bit, so you need to test appropriately.
Function Get-SQLSvrVer {
<#
.SYNOPSIS
Checks remote registry for SQL Server Edition and Version.
.DESCRIPTION
Checks remote registry for SQL Server Edition and Version.
.PARAMETER ComputerName
The remote computer your boss is asking about.
.EXAMPLE
PS C:\> Get-SQLSvrVer -ComputerName mymssqlsvr
.EXAMPLE
PS C:\> $list = cat .\sqlsvrs.txt
PS C:\> $list | % { Get-SQLSvrVer $_ | select ServerName,Edition }
.INPUTS
System.String,System.Int32
.OUTPUTS
System.Management.Automation.PSCustomObject
.NOTES
Only sissies need notes...
.LINK
about_functions_advanced
#>
[CmdletBinding()]
param(
# a computer name
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String]
$ComputerName
)
# Test to see if the remote is up
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet) {
$SqlVer = New-Object PSObject
$SqlVer | Add-Member -MemberType NoteProperty -Name ServerName -Value $ComputerName
$base = "SOFTWARE\"
$key = "$($base)\Microsoft\Microsoft SQL Server\Instance Names\SQL"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $ComputerName)
$SqlKey = $regKey.OpenSubKey($key)
try {
$SQLKey.GetValueNames()
} catch { # if this failed, it's wrong node
$base = "SOFTWARE\WOW6432Node\"
$key = "$($base)\Microsoft\Microsoft SQL Server\Instance Names\SQL"
$regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $ComputerName)
$SqlKey = $regKey.OpenSubKey($key)
}
# parse each value in the reg_multi InstalledInstances
Foreach($instance in $SqlKey.GetValueNames()){
$instName = $SqlKey.GetValue("$instance") # read the instance name
$instKey = $regKey.OpenSubkey("$($base)\Microsoft\Microsoft SQL Server\$instName\Setup") # sub in instance name
# add stuff to the psobj
$SqlVer | Add-Member -MemberType NoteProperty -Name Edition -Value $instKey.GetValue("Edition") -Force # read Ed value
$SqlVer | Add-Member -MemberType NoteProperty -Name Version -Value $instKey.GetValue("Version") -Force # read Ver value
# return an object, useful for many things
$SqlVer
}
} else { Write-Host "Server $ComputerName unavailable..." } # if the connection test fails
}
Try this
Invoke-SqlCmd -query "select ##version" -ServerInstance "localhost"
Check all available method to Get the build number of the latest Cumulative Update / Service Pack that has been installed in SQL Server
Here is a version I cobbled together from some sources here and there*.
This version does not hit the registry, does not hit SQL, and doesn't even require that the instance be running. It does require that you know the instance name. If you don't know the instance name, you should be able to trivially work it out from this code.
To get this to work, replace "YourInstanceNameHere" with the name of your instance. Don't touch the $ if you do it won't work.
$ErrorActionPreference = "Stop"
$instanceName = "MSSQL`$YourInstanceNameHere"
$sqlService = Get-Service -Name $instanceName
$WMISQLservices = Get-WmiObject -Class Win32_Product -Filter "Name LIKE 'SQL Server % Database Engine Services'" | Select-Object -Property Name,Vendor,Version,Caption | Get-Unique
foreach ($sqlService in $WMISQLservices)
{
$SQLVersion = $sqlService.Version
$SQLVersionNow = $SQLVersion.Split("{.}")
$SQLvNow = $SQLVersionNow[0]
$thisInstance = Get-WmiObject -Namespace "root\Microsoft\SqlServer\ComputerManagement$SQLvNow" -Class SqlServiceAdvancedProperty | Where-Object {$_.ServiceName -like "*$instanceName*"} | Where-Object {$_.PropertyName -like "VERSION"}
}
$sqlServerInstanceVersion = $thisInstance.PropertyStrValue
if ($sqlServerInstanceVersion)
{
$majorVersion = $thisInstance.PropertyStrValue.Split(".")[0]
$versionFormatted = "MSSQL$($majorVersion)"
}
else
{
throw "ERROR: An error occured while attempting to find the SQL Server version for instance '$($instanceName)'."
}
$versionFormatted
*I also received help from and help from this this friend of mine https://stackoverflow.com/users/1518277/mqutub and I didn't want it to go uncredited.
All you need is to connect to SQL Server and run this query:
select ##version
This, of course, will work for any client tool.
Additionally, this is also available:
SELECT SERVERPROPERTY('productversion'),
SERVERPROPERTY ('productlevel'),
SERVERPROPERTY ('edition')
More ways to determine the SQL Server version here: http://support.microsoft.com/kb/321185
Just an expansion of Ben Thul's answer, It loops through a list of all my DB Servers and prints out the current version of the database engine:
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
$computers = #(‘XXXX-OMG-DB-01’,’XXXX-PRO-DB-01’,’XXXX-PRO-DB-02’,
’XXXX-QAT-DB-01', 'XXXX-TST-DB-01’,'YYYY-PRO-DB-01',
'YYYY-PRO-DB-02','YYYY-QAT-DB-01','YYYY-QAT-DB-02',
'YYYY-TST-DB-01','ZZZZ-DEV-DB-01','ZZZZ-DEV-DB-02')
$computers | % {
$srv = New-Object "Microsoft.SqlServer.Management.Smo.Server" $_
if ($null -eq $srv.ComputerNamePhysicalNetBIOS) {
$s = $_.tostring() + ' is unavailable'
$s.tostring()
} else {
$srv.ComputerNamePhysicalNetBIOS + ' ' +
$srv.VersionString + ' ' +
$srv.DatabaseEngineEdition
}
}
Well, here's the old school way, that's easy:
sqlcmd -Q "select ##version;"
And here's how I use it from Serverspec:
require 'windows_spec_helper'
describe 'MS SQL Server Express' do
describe service('MSSQLSERVER') do
it { should be_enabled }
it { should be_running }
end
describe port(1433) do
it { should be_listening }
end
describe command('sqlcmd -Q "select ##version;"') do
its(:stdout) { should match /Microsoft SQL Server 2008 R2 (SP2) - 10.50.4000.0 (X64)/ }
end
end