Wbadmin & powershell - latest backup version identifier - powershell

I need to get the latest backups version identifier in a powershell script. If I run wbadmin get versions, I get a list of backups and the last one is the one I need.
Is there a way to do a kind of select top 1 version identifier from backups order by date or parsing the wbadmin output and getting this.
edit
It may be the windows.serverbackup module and versionId of Get-WBBackupSet I'm looking for but still need help parsing this.
VersionId : 04/17/2013-21:00
BackupTime : 17/04/2013 22:00:55
BackupTarget : U:
RecoverableItems : Volumes, SystemState, Applications, Files, BareMetalRecovery
Volume : {System Reserved, Local disk (C:), Local disk (I:), Local disk (O:)...}
Application : {"Cluster", "Registry", "Microsoft Hyper-V VSS Writer"}
VssBackupOption : VssFullBackup
SnapshotId : 58999c7d-dfbf-4272-a5b9-21361d171486

Give this a try, Use -Last instead of -First to get the last item:
Get-WBBackupSet |
Sort-Object BackupTime |
Select-Object -First 1 -ExpandProperty VersionId
You can also play with the order of sorting with the -Ascending switch

Edit: revised version
For use with mixed environments (Windows Server 2008, 2008R2, 2012, 2012R2 as of this writing):
function Get-MyWBSummary
{
<#
.SYNOPSIS
Retrieves the history of backup operations on the local or any number of remote computers.
.DESCRIPTION
The Get-MyWBSummary cmdlet retrieves the history of backup operations on the local or any number of remote computers with remoting enabled. This information includes backuptime, backuplocation, bersion identifier and recovery information.
To use this cmdlet, you must be a member of the Administrators group or Backup Operators group on the local or remote computer, or supply credentials that are.
.PARAMETER ComputerName
Retrives backup results on the specified computers. The default is the local computer.
Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more computers. To specify the local computer ignore the ComputerName parameter.
This parameter rely on Windows PowerShell remoting, so your computer has to be configured to run remote commands.
.PARAMETER Credential
Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01", "Domain01\User01", or User#Contoso.com. Or, enter a PSCredential object, such as an object that is returned by the Get-Credential cmdlet. When you type a user name, you are prompted for a password.
.PARAMETER Last
Specifies the last (newest/latest) backup versions.
.EXAMPLE
Get-MyWBSummary
Retrieves all Windows Server backupversions from the local computer
.EXAMPLE
Get-MyWBSummary | Where BackupTime -gt (Get-Date).AddDays(-7)
Retrieves all Windows Server backupversions from the local computer within the last week
.EXAMPLE
Get-MyWBSummary -ComputerName $server1, $server2 -Last 1 -Credential $credential -ErrorAction SilentlyContinue -ErrorVariable sessionErrors
Retrieves the last (newest) Windows Server Backup backupversion from remote servers $server1 and $server2
.NOTES
Written by Anders Præstegaard (#aPowershell).
Version 1.0 (20-01-2016)
#>
[CmdletBinding()]
[OutputType([PSCustomObject])]
param
(
[string[]]$ComputerName = $env:COMPUTERNAME,
[System.Management.Automation.PSCredential]$Credential,
[int]$Last
)
begin
{
if ($Credential)
{
$PSDefaultParameterValues['New-PSSession:Credential'] = $Credential
}
$psSession = New-PSSession -ComputerName $ComputerName
}
Process
{
$scriptBlock = {
if (-not (Test-Path -Path 'C:\Windows\System32\wbadmin.exe'))
{
## Windows Server Backup not installed
continue
}
$content = WBAdmin.exe GET VERSIONS
if (-not $content)
{
## no versions found
continue
}
## Get linenumbers for each entity
$newJobLines = #($content | Select-String -Pattern 'Backup time: ')
if ($Using:Last -and $using:Last -lt $newJobLines.Count)
{
$newJobLines = $newJobLines[- $using:Last.. -1]
}
$newJobLines |
ForEach-Object{
## Location
$lineNumberLocation = $_.LineNumber
$backupLocation = $content[$lineNumberLocation] -replace 'Backup location: '
## Version Identifier
$lineNumberVersionIdentifier = $_.LineNumber + 1
$backupVersionIdentifier = $content[$lineNumberVersionIdentifier] -replace 'Version identifier: '
## Backuptime UTC
# Version identifier string in WBAdmin output represents the UTC datetime formated in 'MM/dd/yyyy-HH:mm'
$wbAdminDateStringFormat = 'MM\/dd\/yyyy-HH:mm'
$backupDateTimeFromVersionIdentifier = [DateTime]::ParseExact($backupVersionIdentifier, $wbAdminDateStringFormat, $null)
$backupDateTimeUtcSpecified = [DateTime]::SpecifyKind($backupDateTimeFromVersionIdentifier, [System.DateTimeKind]::Utc)
# NB WBAdmin calculates the time statically compared to your timezone (ie +1 hour)
# If your timezone support "Daylight Saving Time" then WBAdmin calculation is wrong
# ~ half of the year (as far as I can perceive)
$backupDateTimeLocalTime = $backupDateTimeUtcSpecified.ToLocalTime()
## Can recover
$lineNumberCanRecover = $_.LineNumber + 2
$backupVersionCanRecover = $content[$lineNumberCanRecover] -replace 'Can recover: '
[PSCustomObject]#{
BackupTime = $backupDateTimeLocalTime
BackupTimeUtc = $backupDateTimeUtcSpecified
BackupLocation = $backupLocation
VersionIdentifier = $backupVersionIdentifier
CanRecover = $backupVersionCanRecover
}
}
} # Scriptblock
Invoke-Command -Session $psSession -ScriptBlock $scriptBlock |
Select-Object -Property * -ExcludeProperty RunspaceId
}
end
{
if ($psSession)
{
Remove-PSSession -Session $psSession
}
}
}

Related

Change a Windows product key remotely with PowerShell

I'm trying to install/activate a MAK key on remote servers. All of them have RemotePS enabled and firewall exception rules in place.
$Results = Invoke-Command -ComputerName Server1 {
$Props = #{ComputerName = $env:ComputerName}
slmgr.vbs /ipk "12345-12345-12345-12345-12345"
$LicStatus = slmgr.vbs /dlv
$Props.Add('LicenseStatus',$LicStatus)
New-Object -TypeName PSObject -Property $Props
}
$Results | Select-Object ComputerName,LicenseStatus
The above does install the MAK key but I don't get any confirmation of this process which is why I've tried adding in the license check option (/dlv) but get nothing returned in the LicenseStatus field. I'm assuming this is because it returns a multi-value maybe!?
Ultimately I'm just trying to get confirmation that the key was installed. There are articles out there about performing this using RemotePS but they all say a notification message is returned for each computer which isn't the case in my experience: https://4sysops.com/archives/change-a-product-key-remotely-with-powershell/
Any ideas how I can check this?
I would call the slmgr.vbs script using Cscript.exe in order to get the results as string array. Otherwise the system will default to using Wscript.exe which is designed to output everything in a messagebox.
Unfortunately, all output of slmgr is localized, so using a regex or something on the LicenseStatus is a no go (on a Dutch NL machine it reads 'Licentiestatus')
What you can do is using switch /dli, because that returns a string array where the last (not empty) value has the status.
Try
$Results = Invoke-Command -ComputerName Server1 {
# install MAK key
$null = cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /ipk "12345-12345-12345-12345-12345"
# test LicenseStatus
$LicStatus = (((cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /dli) |
Where-Object { $_ -match '\S' })[-1] -split ':', 2)[1].Trim()
# return an object
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
LicenseStatus = $LicStatus
}
}
$Results

Remote registry key extractor PowerShell script

I am trying to create a PowerShell script that remotely checks each machine on the domain for a registry key entry, then outputs that key value along with the machine name to a .csv file.
So far the script outputs all the machines on the domain to a .csv file but puts its local registry key value not the value of the remote machine.
Any help would be greatly appreciated, here is what I have so far.
Import-Module ActiveDirectory
$SRVS = Get-ADComputer -Filter * -SearchBase 'DC=mydomain,DC=local' |
select dnshostname
foreach ($SRV in $SRVS) {
$REG = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SRV.name)
$REGKEY = $REG.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat")
$MELT = $REGKEY.GetValue('cadca5fe-87d3-4b96-b7fb-a231484277cc')
"$($SRV);$($MELT)" | Out-File C:\Users\user1\Desktop\regkeys.CSV -Append
}
The statement
$SRVS = Get-ADComputer ... | select dnshostname
gives you a list of custom objects with only one property: dnshostname. But in your loop you're trying to use a property name, which those custom objects don't have. Hence the statement
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SRV.name)
effectively becomes
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $null)
meaning you're opening the local registry, not the registry on a remote host.
Change $SRV.name to $SRV.dnshostname and the problem will disappear.
Once it's been instanced the RegistryKey class does not expose that it's a remote key. That means you have to record the computer name yourself. There's also no standard format for a remote registry value.
If I had a PowerShell v5+, I would use something like this:
Import-Module ActiveDirectory
# No need for the Select-Object here since we're using $SRV.Name later
$SRVS = Get-ADComputer -Filter * -SearchBase 'DC=mydomain,DC=local'
# Create an arraylist to save our records
$Report = New-Object System.Collections.ArrayList
# This try finally is to ensure we can always write out what we've done so far
try {
foreach ($SRV in $SRVS) {
# Test if the remote computer is online
$IsOnline = Test-Connection -ComputerName $SRV.Name -Count 1 -Quiet;
if ($IsOnline) {
# If system is Online
$REG = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SRV.name)
$REGKEY = $REG.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat")
$MELT = $REGKEY.GetValue('cadca5fe-87d3-4b96-b7fb-a231484277cc')
# Create a PSObject record for convenience
$Record = [PSCustomObject]#{
ComputerName = $SRV;
Key = $REGKEY.Name;
Value = $MELT;
}
}
else {
# If system is Offline
# Create a PSObject record for convenience
$Record = [PSCustomObject]#{
ComputerName = $SRV;
Key = '';
Value = '';
}
}
# Add our record to the report
$Report.Add($Record);
}
}
finally {
# Always write out what we have whether or not we hit an error in the middle
$Report | Export-Csv -Path "C:\Users\user1\Desktop\regkeys.csv" -NoTypeInformation
}
That may work on PowerShell v3+, but I don't have it around anymore to test.
Any reason you are trying to printout the actual regkey vs just checking for it's existence?
It either exists or it does not. Say using something like...
Clear-Host
Import-Module ActiveDirectory
$SRVS = (Get-ADComputer -Filter * -SearchBase (Get-ADDomainController).DefaultPartition)
$MeltHive = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat'
$MeltHiveKey = 'cadca5fe-87d3-4b96-b7fb-a231484277cc'
ForEach($Srv in $SRVS)
{
Invoke-Command -ComputerName $Srv.Name -ScriptBlock {
If (Get-ItemProperty -Path $Using:MeltHive -Name $MeltHiveKey -ErrorAction SilentlyContinue)
{"On Host $env:COMPUTERNAME MELT registry information exists"}
Else {Write-Warning -Message "On host $env:COMPUTERNAME MELT registry information does not exist"}
}
}
ForEach($Srv in $SRVS)
{
Invoke-Command -ComputerName $Srv.Name -ScriptBlock {
If ((Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion) -match 'QualityCompat')
{"On Host $env:COMPUTERNAME MELT registry information exists"}
Else {Write-Warning -Message "On host $env:COMPUTERNAME MELT registry information does not exist"}
}
}
Results of both the above is:
WARNING: On host DC01 MELT registry information does not exist
WARNING: On host EX01 MELT registry information does not exist
WARNING: On host SQL01 MELT registry information does not exist
On Host IIS01 MELT registry information exists

PowerShell WMI query fails to return username in logon script

I'm trying to get the username of domain users in a PowerShell logon script. Any number of different users may log into the computers in question.
A local user account (let's call it 'syscheck') is configured on Win7/Win8 domain clients for the purpose of running a PS script (PS 2.0/3.0); the script resides locally and is launched by Task Scheduler on user logon. The script needs to obtain the username of the domain user that is logging in.
I've attempted to do this with WMI:
Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty UserName
but this does not return anything when the script runs.
If I try this:
$env:USERNAME
The username of the 'syscheck' local account is returned.
Is the domain username not yet available when the script is running on logon?
Perhaps there a way to do this with .NET? Other options?
***** UPDATE August 8 *****
I've tested with the solution provided (thanks Alexander!) but still can NOT retrieve the username of the logged-in user. I believe this is because, as mentioned above, this is a logon script launched by Task Scheduler. The principal for the Task that launches the script is a local account. For some reason, all methods of trying to get the domain username fail.
Here is latest attempt:
First, this is how I call the function:
$indx = 0
do {
$username = GetDomUser
if (($indx -eq 25) -or ($username.Length -ne 0)) {
Write-Output $username
Break
}
else {
Start-Sleep -Seconds 12
}
$indx++
}
while ($indx -lt 25) # 5 minutes is PLENTY of time for boot...
Now, here's the function:
Function GetDomUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"MYDOMAIN",Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } | Select-Object -ExpandProperty Antecedent)
Return ([regex]::Match([string]$antecedent[0],"$pattern(.*$)").Value).Split('=')[1] -replace '"', ""
}
Of course, this works perfectly from the console once the machine has booted.
Is it possible to refresh whatever store the Win32_LoggedOnUser Class gets its data from?
Other options?
Here are previous methods I've tried - all return the username of the principal of the Task that launches the script (or an empty string, which is what D returns).
$usernameA = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
$usernameB = $(whoami)
$usernameC = $($env:USERNAME)
$usernameD = $(Get-WmiObject Win32_ComputerSystem -ComputerName $compname | Select-Object -ExpandProperty UserName)
$usernameE = $([Environment]::UserName)
Here's what you could do to find out what's going on:
$iLOGON32_LOGON_INTERACTIVE = 2
$cLogonSessions = Get-WmiObject -Class "Win32_LogonSession" `
| Where-Object { $_.LogonType -eq $iLOGON32_LOGON_INTERACTIVE }
if ($cLogonSessions -ne $null) {
$cInteractiveLogons = #()
foreach ($oLogonSession in $cLogonSessions) {
$sWmiQuery = ('ASSOCIATORS OF {{Win32_LogonSession.LogonId="{0}"}} ' `
+ 'WHERE AssocClass=Win32_LoggedOnUser') -f $oLogonSession.LogonId
$cInteractiveLogons += Get-WMIObject -Query $sWmiQuery `
| Select-Object -ExpandProperty "Caption"
}
} else {
$ex = New-Object -TypeName System.NullReferenceException(`
'$cInteractiveLogons is null.')
throw $ex
}
$cInteractiveLogons | Select-Object -Unique
When $cInterativeLogons is null exception is thrown, it means that no-one is logged on interactively (yet) in which case you can wait and re-check later.
Note that this code is not reliable because LOGON32_LOGON_INTERACTIVE wasn't limited to local console logons in XP and earlier versions.
As for actual solution, I'd recommend using some kind of explicit notifications. You could for example make use of events. Subscribe for an event and then emit the event from the user's regular logon script.
The problem was not with the WMI code but rather the state of the machine it was being run on. It turns out that when users are VPNed into their machines (almost always thanks to a VPN client's automated reconnect feature), or have some third-party utility installed (e.g. certain cloud backup services), there are multiple Logons and "the" logged on user is ambiguous.
For now this is working pretty well:
Function GetDomainUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"' + $($env:USERDOMAIN) + '"' + ',Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } |
Select-Object -ExpandProperty Antecedent | Select-Object -Unique)
Return $(([regex]::Match([string]$antecedent,$($pattern + '(".+")')).Value).Split('=')[1] -replace '"','')
}
But I had to write addition code to work around cases when the LoggedOnUser cannot be discovered (multiple logons exist), or when no one is logged in.

How to verify whether a windows server has mountpoint or not using WMI

I am generating a report where I need to find which servers has mountpoints configured on it..
can you help how to get that infor using WMI or powershell.
I mean I need to identify the servers, if mountpoints exists in it.. and also their names....
Get a list of all servers from textfile, AD, etc. and run a foreach loop with something like this:
Get-Wmiobject -query “select name,driveletter,freespace from win32_volume where drivetype=3 AND driveletter=NULL” -computer servername
A quick google search for "windows mount point wmi" would return THIS (source).
Then export the results to CSV, HTML or whatever you need. Your question is lacking a lot of details and any sign of effort from your part, so I can't/won't go any further.
UPDATE: Does this help? It lists mount points(folder paths, not driveletters).
$servers = #("server1","server2","server3","server4","server5")
$servers | % {
$mountpoints = #(Get-WmiObject Win32_MountPoint -ComputerName $_ | Select-Object -ExpandProperty Directory | ? { $_ -match 'Win32_Directory.Name="(\w:\\\\.+)"' }) | % { [regex]::Match($_,'Win32_Directory.Name="(\w:\\\\.+)"').Groups[1].Value -replace '\\\\', '\' }
if($mountpoints.Count -gt 0) {
New-Object psobject -Property #{
Server = $_
MountPoints = $mountpoints
}
}
}
Server MountPoints
------ -----------
{server1} {D:\SSD, C:\Test}

Formatting the output of repadmin - powershell

I'm creating a script that tells me the creation / modification date and other pieces of info of AD objects to determine upgrade status of the machines in large domains. I have no problem accomplishing this in a well formatted and easy to read manner in Server 2008 because it has Active Directory modules, but this isn't the case with Server 2003.
With server 2003 I had to use a different approach to the script to gather the information I want, but I am unsure how to format this.
This is my current script:
$filePath = “$ENV:UserProfile\Desktop\output.txt”
## Enter the name of the Domain controller below
$DCName = “Exchange”
$computers = dsquery computer domainroot -name * -limit 0
Foreach ($computer in $computers) {
repadmin /showattr $DCName $computer /atts:"WhenChanged,WhenCreated,OperatingSystem" /long | out-file –append $filepath
}
This is the sample output:
DN: CN=Sample-Object,CN=Computers,DC=Contoso,DC=com
1> whenCreated: 07/04/2011 14:00:02 Pacific Standard Time Pacific Daylight Time
1> whenChanged: 08/09/2012 11:24:22 Pacific Standard Time Pacific Daylight Time
1> operatingSystem: Windows 7 Professional
In server 2008 I'm able to use string formatting ('"{0}","{1}"' -F $computer.name, $computer.whatever) amd output it to a .csv to make it presentable but I don't think the same methods will apply to the results of repadmin.
My end goal would to simply have a CSV with Computer Name, along with the three or however many attributes I have extracted from repadmin.
Any help appreciated, thank you.
Give this a try, you can export it to CSV and import it back as objects:
$result = dsquery computer domainroot -name * -limit 0 | foreach {
repadmin /showattr $DCName $_ /atts:"WhenChanged,WhenCreated,OperatingSystem" /long
} | Out-String
$result.split([string[]]'DN:',[StringSplitOptions]::RemoveEmptyEntries) | Foreach-Object{
$attr = $_.Split("`r`n",[StringSplitOptions]::RemoveEmptyEntries)
New-Object -TypeName PSObject -Property #{
DN = $attr[0].Trim()
whenCreated = $attr[1].Trim() -replace '^1> whenCreated: '
whenChanged = $attr[2].Trim() -replace '^1> whenChanged: '
operatingSystem = $attr[3].Trim() -replace '^1> operatingSystem: '
}
}