Automatizar la configuración con FC de una FlashArray //X en HyperV

Pues como el propio título indica, cuando hemos de hacer una configuración de una FlashArray //X en un entorno Hyper-V y mediante conexiones Fiber Channel, he creado un script en Powershell que automatiza el proceso de instalación y configuración, siguiendo las best practices que podéis encontrar en este enlace del centro de ayuda de Purestorage.

La verdad es que se podria hacer todo en entorno gráfico, pero mediante scripting es muchisimo más rápido y desatentido que no abriendo y cerrando ventanas de Windows. En todo caso, vamos a explicar los pasos de la configuración y el porqué de todo ello para, al final del post tener el script entero.

Instalación del MPIO (Multipath-I/O)

Esta característica de Windows se debe instalar para poder dotar al servidor o nodo Hyper-V distintas rutas (paths) hacia el almacenaje que presenta la FlashArray. Si uno de estas rutas (iSCSI o HBA) cae, la característica MPIO redirigirá las peticiones por otra ruta, sin que el acceso a los datos se vea afectado.

Configurar el MPIO

Una vez instalada la caracterísitca, se debe añadir el VendorId y el ProductID en un formato de 8 y 16 carácters para posteriormente, modificar los timers del MPIO para obtener el rendimiento óptimo en nuestra FlashArray. Los valores que se deben modificar son:

  • NewPathRecoveryInterval: es el tiempo que el servidor tarda en recuperar la ruta perdida. El valor por defecto es 40 y con FlahArray lo debemos cambiar a 20 segundos
  • CustomPathRecovery: es una variable booleana (1 o 0) que activa (1) o desactiva (0) la recuperación de la ruta personalizada, es decir, el primer parámetro que vamos a modificar. Con FlashArray el valor debe ser 1
  • PDORemovePeriod: un PDO es un objeto de dispositivos físicos (Physical Device Object). Este valor es el tiempo que espera el servidor a eliminar todas las rutas que han fallado hacia un PDO. El valor por defecto es 20 y con FlashArray lo debemos aumentar a 30
  • NewDiskTimeout: es el valor en segundos del tiempo de espera del disco. Este valor es el tiempo que esperará el servidor antes de marcar la solicitud E/S como fallida. En este caso se mantiene en 60 segundos, aunque la documentación de Microsoft lo establezca en 120
  • PathVerificationState: este valor nos indica si debemos verificar que el path está activo o no. Por defecto viene en Disabled y lo deberemos poner en Enabled

Configurar la política del MPIO

En este caso, y resumiendo, se refiere a como el servidor Hyper-V o host, distribuye las IOs entre las distintas rutas hacia el almacenaje. En este caso nos encontramos con que hay distintos tipos de politicas:

  • FOO: Fail Over Only
  • RR: Round Robin
  • LQD: Last Queue Depth
  • LB: Least Blocks
  • None: la que venga por defecto

En el caso de Pure Storage, la política de acceso debe ser Round Robin (RR)

Configurar la SAN Policy

Bàsciamente esto se refiere a como Windows Server determina si los discos deben montarse de forma automática cuando se detectan como nuevos en el host. En este caso si el host no forma parte de un Windows Server Failover Clustering, se debe cambiar la política de OfflineShared a OnlineAll para poner en linia los volúmenes existentes cuando se reinicie el host

Explicado todas las modificaciones que debemos hacer en un host Hyper-V, vamos a a ver como automatizarlo todo en un script. En primer lugar, los cmdlets que vamos a necesitar son los siguientes:

  • Instalación del MPIO (Multipath-I/O)

Get-WindowsFeature -Name 'Multipath-IO'

Add-WindowsFeature -Name 'Multipath-IO'

  • Configurar el MPIO

Remove-MSDSMSupportedHw -VendorId 'Vendor' -ProductId 'Product'

New-MSDSMSupportedHw -VendorId PURE -ProductId FlashArray

Set-MPIOSetting -NewPathRecoveryInterval 20 -CustomPathRecovery Enabled -NewPDORemovePeriod 30 -NewDiskTimeout 60 -NewPathVerificationState Enabled

  • Configurar la política del MPIO

Set-MSDSMGlobalDefaultLoadBalancePolicy -Policy RR

  • Configurar la SAN Policy

Set-StorageSetting -NewDiskPolicy OnlineAll

Una vez tengamos claros los cmdlets con las opciones que Pure Storage nos recomienda para los entorns HyperV vamos a construir el script. Como punto de partida, debemos pensar que hay dos reinicios del host, y por lo tanto si quieremos que sea una configuración automática y desatendida, deberemos re-arrancar el script una vez arranque el servidor y nos logueemos de nuevo. Este script seguro que no es la mejor forma de hacerlo, pero es la que suelo usar yo en instalaciones desatendidas o semi-desatendias, siendo muy fácil su gestión.

El truco radica en crear un contador ($bootcount) que irá ejecuntando las opciones del menú switch {}, generando en un directorio %\temp un fichero *ps1 con las opciones y un fichero *bat que ejecutará el script. No soy programador profesional así que si encontráis fallos, estaré encantado de recibir vuestras (constructivas) críticas.

Script:

# Create a 'temp' Folder
if (!(Get-Item C:\temp -ea ignore)) { mkdir C:\temp }

$dropperscript = 'C:\temp\FlashArrayHV_Conf.ps1'

$dropper = @'

$countfile = 'C:\temp\bootcount.txt'
$bootbatch = 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\dropper.bat'
$dropperscript = 'C:\temp\dropper.ps1'

# Bootstrap Batch
if (!(Get-Item $bootbatch -ea ignore)) {
    "powershell -c $dropperscript`npause" | Out-File $bootbatch -Encoding 'OEM'
}

# Boot Count
if (Get-Item $countfile -ea ignore) {
    [int]$bootcount = Get-Content $countfile
    if ($bootcount -match "^\d{1,2}$") { ([int]$bootcount) ++ }
    else { $bootcount = 1 }
}
else { $bootcount = 1 }
$bootcount | Out-File $countfile

switch ($bootcount) {
    
    1 {
            Get-WindowsFeature -Name 'Multipath-IO'

            Add-WindowsFeature -Name 'Multipath-IO'
       
            Restart-Computer
                
    }
    
    2 {
            Remove-MSDSMSupportedHw -VendorId 'Vendor*' -ProductId 'Product*'
        
    New-MSDSMSupportedHw -VendorId PURE -ProductId FlashArray
            Get-MSDSMSupportedHw

            Restart-Computer
                
    }
    
    3 {
    
        Set-MPIOSetting -NewPathRecoveryInterval 20 -CustomPathRecovery Enabled -NewPDORemovePeriod 30 -NewDiskTimeout 60 -NewPathVerificationState Enabled
        Get-MPIOSetting
        Set-MSDSMGlobalDefaultLoadBalancePolicy -Policy RR
        Get-MSDSMGlobalDefaultLoadBalancePolicy

        Set-StorageSetting -NewDiskPolicy OnlineAll
        Get-StorageSetting | Select-Object NewDiskPolicy
        Restart-Computer
     }
    
    default {
        # Dropper is complete; clean up
        rm $countfile
        rm $bootbatch
        rm $dropperscript
    }
}
'@

# Drop and run Dropper

$dropper | Out-File $dropperscript -Encoding 'OEM'

Invoke-Expression $dropperscript

Powershell: creación y eliminación de snapshots Hyper-V

índice

En un entorno virtual, es una muy buena práctica tener controlados los snapshots de nuestras máquinas virtuales. A diferencia de VMWare, en Hyper-V són puntos de control que se crean muy rápidamente y se consolidan de la misma forma. Es per ello que es una muy buena opción a contemplar como complemento de los backups.

En un caso concreto yo lo uso como estrategia de prevención contra Cryptolockers y ransomware varios haciendo que cada día, antes de que las empresas entren a trabajar se cree un punto de control. Para ello uso una tasca programada con el siguiente script:

Get-VM  * | checkpoint-vm -SnapshotName "[Nombre que le queramos dar] $((Get-Date).toshortdatestring())" –AsJob

Con este script en powershell, cada día se nos ejecutará un punto de control de TODAS nuestras máquinas virtuales. Podemos acotar la búsqueda o filtrar con el comando:

Get-VM –Name [Nombre de la M]

y que podremos filtrar. Por ejemplo: si todas nuestras máquinas virtuales se llaman SRV-[nombre] pero algunas son OLDSRV y solo queremos snapshots de las primeras, podemos hacer:

Get-VM SRV-*

Hecho esto, el siguiente paso es controlar cuantos snapshots queremos guardar. En mi caso solamente guardo un día puesto que como he dicho antes, es una medida de control contra los ransomware, no una backup. Para ello, uso el script siguiente:

Get-VMSnapshot –VMName [nombre_MV]* | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(-1) } | Remove-VMSnapshot

Donde:

  • (Get-Date).AddDays(-1) son los días que quiero almacenar. En mi caso 1. Este valor lo podéis cambiar por el que queráis.
  • [nombre_MV]* es una string del nombre, como si fuera un sufijo. Es para filtrar.

De esta forma, puedo controlar que si nos entra algo que nos engorrine el sistema y es crítico, podemos en tan solo dos clics de ratón, volver al inicio del día. Eso si, para clientes o sistemas más críticos, uso el script varias veces al día y los elimino todos al día siguiente. La secuencia de comandos de la tasca programada es la siguiente:

powershell.exe –ExecutionPolicy Bypass –file “[ruta_del_script]”

Monitorizar y reportar Exchange Online

El otro dia necesitaba una herramienta que me proporcionara estadísticas curradas de las cuentas de usuario de Exchange Online para mi posterior gestión y encontré esta maravilla:

Mail Protection Reports for Office 365

Esta herramienta nos permite, entre otros:

  • Tráfico
  • Correo no deseado
  • Malware
  • Reglas
  • Prevención de pérdida de datos

Para aquellos «sysadmins» que requieran de unos informes curradetes 🙂

Powershell: ¿Cómo sé cuanto ocupan mis buzones en Office 365?

Pues eso: ¿cómo podemos saber cuanto ocupan nuestros buzones de Exchange Online en Office365?

De nuevo, usando Powershell y un script más que estupendo podemos obtener un fichero CSV donde  podemos ver el nombre de usuario, los elementos totales, el tamaño total del buzón en Gb, elementos en la bandeja de entrada y varios campos más 🙂 Importante! Debemos tener PowerShell actualizado a la versión 3.

Solamente copiad el script y guardarlo con la extensión *.ps1. Y recordad que antes de ejecutar el script tenéis que conectaros al Exchange:

  • Revisamos que la política sea «Unrestricted»
Get-ExecutionPolicy
Set-ExecutionPolicy Unrestricted
  • Nos conectamos y nos descargamos los cmdlet
$LiveCred=Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri <a href="https://ps.outlook.com/powershell/">https://ps.outlook.com/powershell/</a> -Credential $LiveCred -Authentication Basic -AllowRedirection
Import-PSSession $Session

Y grabamos el script en un *.ps1.

#requires -version 3
[cmdletbinding()]
Param(
[Parameter(Position=0,
Mandatory=$false,
HelpMessage='Path of the CSV log file.')]
[String]$LogName = 'MailBoxes.csv'#,

#[Switch]$CountAllMailsItems

)

[System.Collections.ArrayList]$Mbx = @()
Write-Verbose -Message "Searching for mailboxes..."
$Mailboxes = Get-MailBox -ResultSize unlimited
[int]$i = 1
[int]$TotalMailboxes = $Mailboxes.count
Write-Verbose -Message "$TotalMailboxes mailboxes found."

ForEach($MailBox in $Mailboxes){
Write-Progress -Activity "$($MailBox.Alias)" -Status "Running" -PercentComplete ([int]$($i/$TotalMailboxes*100))
Write-Verbose -Message "$($MailBox.Alias)..."
$i++

$MailBoxFolderStatistic = Get-MailboxFolderStatistics $MailBox.Identity -IncludeOldestAndNewestItems
$Calendar = $MailBoxFolderStatistic | ? {$_.FolderType -eq 'Calendar'}
$Contacts = $MailBoxFolderStatistic | ? {$_.FolderType -eq 'Contacts'}
$Inbox = $MailBoxFolderStatistic | ? {$_.FolderType -eq 'Inbox'}
$DeletedItems = $MailBoxFolderStatistic | ? {$_.FolderType -eq 'DeletedItems'}
$SentItems = $MailBoxFolderStatistic | ? {$_.FolderType -eq 'SentItems'}

<#
if($CountAllMailsItems){
$Mails = $MailBoxFolderStatistic | ? {($_.FolderType -eq 'User Created') -OR ($_.FolderType -eq 'Inbox') -OR ($_.FolderType -eq 'SentItems') -OR ($_.FolderType -eq 'Boite de récéption')}

[int]$ItemsInFolder = 0
ForEach($Folder in $Mails){
$ItemsInFolder += $Folder.ItemsInFolder
$FolderSize += $Folder.FolderSize
}
}
else{
$ItemsInFolder = "N/A"
$FolderSize = "N/A"
}
#>

$MailBoxStatistic = Get-MailboxStatistics $MailBox.Identity

$Mbx.Add([PSCustomObject][Ordered] @{
#Mailbox
Name = $MailBox.Name
TotalItems = $MailBoxStatistic.ItemCount
TotalSize = $MailBoxStatistic.TotalItemSize
#Mails
#TotalMail = $ItemsInFolder
#TotalMailSize = $FolderSize
SizeInMB = (($MailBoxStatistic.TotalItemSize.Value.ToString().split('(')[-1]).split(' ')[0]).Replace(',','') /1MB -as [int]
InboxItem = $Inbox.ItemsInFolder
InboxItemSize = $Inbox.FolderSize
LastReceive = $Inbox.NewestItemReceivedDate
FirstReceive = $Inbox.OldestItemReceivedDate
DeletedItems = $DeletedItems.ItemsInFolderAndSubfolders
DeletedItemsSize = $MailBoxStatistic.TotalDeletedItemSize
SentItems = $SentItems.ItemsInFolder
SentItemsSize = $SentItems.FolderSize
#Calendar
TotalCalendar = $Calendar.ItemsInFolder
TotalCalendarSize = $Calendar.FolderSize
#Contacts
Contacts = $Contacts.ItemsInFolder
#Divers
LastLogon = $MailBoxStatistic.LastLogonTime
Language = $MailBox.Languages
}) | Out-Null

$ItemsInFolder = $FolderSize = $MailBoxFolderStatistic = $Calendar = $Contacts = $Inbox = $DeletedItems = $SentItems = $MailBoxStatistic = $null
}
$Mbx | Export-Csv -NoTypeInformation -Delimiter ';' $LogName

Administrar Active Directory con Powershell: modificar la carpeta personal del perfil de usuario

El otro dia me encontré con un problema bastante «peasado» para resolver. En casa de un cliente, hicimos una migación de servidor de dominio de un Win2k3 hacia un Win2k8R2 con un Win2k12R2 como soporte secundario. La migración fue perfecta pero repasando los perfiles de usuario del ActiveDirectory, vi que muchos de ellos en su perfil tenian configurada una ruta hacia una carpeta personal que ya no servia de nada.

Problema: como hago para poder eliminar la ruta configurada en el perfil de usuario sin ir uno a uno, botón derecho, propieades, perfil … para unos 80 usuarios?

Solución: mi queridíssimo Powershell 🙂 Empezamos!

  • Primer paso (importante): Ejecutar powershell desde nuestro servidor controlador de dominio y tener instalado los modulos de gestión del ActiveDirectory (por defecto cuando promocionamos un servidor, se instalan).
AddWindowsFeature RSAT-AD-PowerShell
Import-Module ActiveDirectory
  • Segundo Paso: Una vez instalado e importado el módulo, deberemos sacar la información de los usuarios. Para ello usaremos el comando Get-ADUser. Pero atención, si lo usamos a «paloseco», se nos pedirà que apliquemos un filtro de búsqueda. Para que hagáis la prueba, ejecutad el siguiente comando:
Get-ADUser -Filter *

El parámetro «-Filter *» nos va a mostrar todos los usuarios del AD.

  • Tercer paso: ahora que sabemos buscar los usuarios, deberemos saber la información contenida en dicho usuario. Para ello usaremos el parámetro «-properties *». Esto nos sacará TODAS las propiedades del usuario. Con ello sabremos que campo modificar:
Get-ADUser [nombre de usuario con el que se loguea] -propierties *

Si nos fijamos en el resultado, ya vemos que campo queremos modificar en nuestro caso concreto: HomeDirectory. Sabiendo esto, podemos acotar aún más el filtro:

Get-ADUser [nombre de usuario con el que se loguea] -properties HomeDirectory
  • Quarto paso:Ahora teniendo ya toda la información, procedemos a la modificación del parámetro HomeDirectory para TODOS los usuarios del ActiveDirectory y lo dejaremos sin ninguna ruta:
Get-ADUser -Filter * -properties HomeDirectory | Set-ADUser -Clear HomeDirectory

Y listos! Nos hemos ahorrado ir usuario a usuario modificando el HomeDirectory y lo hemos podido hacer en solo una línea de código. Ahora bien, si somos un poco ordenados y tenemos inventariadas las rutas de las carpetas locales, podemos hacer filtros y modificar solamente aquellas rutas que nos interesen. Por ejemplo, si solo quiero modificar la ruta Z:\empresa\perfiles\usuarios\ usaría el siguiente comando:

Get-ADUser -Filter * -properties HomeDirectory | where {$_.homedirectory -like "Z:\empresa\perfiles\usuarios" | Set-ADUser -Clear HomeDirectory

O si quiero cambiar a otro directorio de red:

Get-ADUser -Filter * -properties HomeDirectory | where {$_.homedirectory -like "[Directorio Viejo" | Set-ADUser -HomeDirectory "[Directorio Nuevo]"

PowerShell si lo sabemos usar o si queremos administrar nuestro dominio de una forma sencilla y rápida, es una herramienta muy poderosa. Además, siempre podemos tener nuestro repositorio de comandos en un OneNote para tenerlos a mano. En mi caso, no siempre me acuerdo de todo 😛

Espero haberos ayudado!

Powershell: script para saber las actualizaciones de nuestro servidor

En mi trabajo tengo que administrar un montón de servidores Windows 2008R2 y Windows 2012R2 y muchas veces me pierdo en cuanto ha actualizaciones. Lo que hago en este caso es programarme un script en powershell que me permite saber en cada servidor cuantas actualizaciones hay y como son (críticas o opcionales) con lo que llevo un pequeño registro y control de las mismas. Obviamente, lo que no os recomiendo es actualizar enseguida i cada semana. Lo suyo en cuanto a servidores es actualizar cada 30 o 60 días.

Os dejo el script 🙂


'==========================================================================
' NOMBRE: Notificaciones automáticas via mail de actualizaciones
' AUTOR: Paul Murana
'==========================================================================
'Cambiad estas variables para controlar los criterios de los avisos del mail
'y para configurar el servidor y los campos para/de
AlertCritical = 1
AlertImportant = 1
AlertModerate = 1
AlertLow = 1
AlertOthers = 1
EmailFrom = ""
EmailTo = ""
RemoteSMTPServer= ""
RemoteSMTPPort = "25"
'==========================================================================

Set fso = CreateObject("Scripting.FileSystemObject")
Set updateSession = CreateObject("Microsoft.Update.Session")
Set updateSearcher = updateSession.CreateupdateSearcher()

Set oShell = CreateObject( "WScript.Shell" )
computername = oShell.ExpandEnvironmentStrings("%ComputerName%")
DomainName = oShell.ExpandEnvironmentStrings("%userdomain%")
EMailSubject = "Windows Update Notification - " & DomainName & "\" & computername
Set oshell = Nothing

Set searchResult = updateSearcher.Search("IsInstalled=0 and Type='Software'")

If searchResult.Updates.count > 0 Then
 For I = 0 To searchResult.Updates.Count-1
 Set update = searchResult.Updates.Item(I)
 Select Case update.MsrcSeverity
 Case "Critical"
 CriticalCount = Criticalcount+1
 CriticalHTML = CriticalHTML & MakeHTMLLine(update)
 Case "Important"
 ImportantCount = Importantcount + 1
 ImportantHTML = ImportantHTML & MakeHTMLLine(update)
 Case "Moderate"
 ModerateCount = Moderatecount + 1
 ModerateHTML = ModerateHTML & MakeHTMLLine(update)
 Case "Low"
 Lowcount = Lowcount + 1
 LowHTML = LowHTML & MakeHTMLLine(update)
 Case Else
 Otherscount = Otherscount + 1
 OthersHTML = OthersHTML & MakeHTMLLine(update)
 end select
 Next

 If searchResult.Updates.Count = 0 Then
 WScript.Quit
 Else
 If (AlertCritical=1 and CriticalCount > 0) then SendEmail=1 end if
 If (AlertImportant=1 and ImportantCount > 0) then SendEmail=1 end if
 If (AlertModerate=1 and ModerateCount > 0) then SendEmail=1 end if
 If (AlertLow=1 and LowCount > 0) then SendEmail=1 end If
 If (AlertOthers=1 and OthersCount > 0) then SendEmail=1 end If
 if SendEmail=1 Then
 Wscript.Echo "sendEmail"
 Set objMessage = CreateObject("CDO.Message")
 objMessage.Subject = EMailSubject
 objMessage.From = EmailFrom
 objMessage.To = EmailTo
 objMessage.HTMLBody = ReplaceHTMLTemplate()
 if RemoteSMTPServer <> "" then
 objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
 objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = RemoteSMTPServer
 objMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = RemoteSMTPPort
 objMessage.Configuration.Fields.Update
 end if
 objMessage.Send
 end if
 end If
End If

Function MakeHTMLLine(update)
 HTMLLine="<tr><td>" & update.Title & "</td><td>" & update.description & "</td><td>"
 counter =0
 For Each Article in Update.KBArticleIDs
 if counter > 0 then HTMLLine=HTMLLine & "<BR>"
 HTMLLine=HTMLLine & "<a href=" & chr(34) & "http://support.microsoft.com/kb/" & article & "/en-us" & chr(34) & ">KB" & article & "</a>"
 counter = counter +1
 Next
 For Each Info in Update.moreinfourls
 if counter > 0 then HTMLLine=HTMLLine & "<BR>"
 HTMLLine=HTMLLine & "<a href=" & chr(34) & info & chr(34) & ">" & "More information...</a>"
 counter = counter +1
 Next
 HTMLLine = HTMLLine & "</td></tr>"
 MakeHTMLLine = HTMLLine
End function

Function ReplaceHTMLTemplate()
 Set HTMLFile = fso.opentextfile((fso.GetParentFolderName(WScript.ScriptFullName) & "\updatetemplate.htm"),1,false)
 MasterHTML = HTMLFile.Readall
 HTMLFile.close

 MasterHTML = Replace(MasterHTML, "[criticalupdatecontents]", CriticalHTML)
 MasterHTML = Replace(MasterHTML, "[importantupdatecontents]", ImportantHTML)
 MasterHTML = Replace(MasterHTML, "[moderateupdatecontents]", ModerateHTML)
 MasterHTML = Replace(MasterHTML, "[lowupdatecontents]", LowHTML)
 MasterHTML = Replace(MasterHTML, "[othersupdatecontents]", OthersHTML)
 MasterHTML = Replace(MasterHTML, "[computername]", Computername)
 MasterHTML = Replace(MasterHTML, "[domainname]", domainname)
 MasterHTML = Replace(MasterHTML, "[timenow]", now())

 If (CriticalCount = 0) then
 MasterHTML = TrimSection(MasterHTML, "<!--CriticalStart-->", "<!--CriticalEnd-->")
 end if

 If (ImportantCount = 0) then
 MasterHTML = TrimSection(MasterHTML, "<!--ImportantStart-->", "<!--ImportantEnd-->")
 end if

 If (moderateCount = 0) then
 MasterHTML = TrimSection(MasterHTML, "<!--ModerateStart-->", "<!--ModerateEnd-->")
 end if

 If (LowCount = 0) then
 MasterHTML = TrimSection(MasterHTML, "<!--LowStart-->", "<!--LowEnd-->")
 end if
 If (OthersCount = 0) then
 MasterHTML = TrimSection(MasterHTML, "<!--OthersStart-->", "<!--OthersEnd-->")
 end if

 ReplaceHTMLTemplate = MasterHTML
End Function

Function TrimSection(CompleteString,LeftString,RightString)
 LeftChunkPos=inStr(CompleteString, LeftString)
 RightChunkPos=inStrRev(CompleteString, Rightstring)
 LeftChunk=Left(CompleteString, LeftChunkPos-1)
 RightChunk=mid(CompleteString, RightChunkPos)
 TrimSection=LeftChunk & RightChunk
End Function

Guardamos el script en un fichero con extensión VBS (p.ej. yo lo llamo WinUpdates.vbs) y acto seguido lo único que debemos hacer és crear un script cmd o bat para que ejecute el fichero vbs y añadirlo a las tareas programadas.


cscript C:\WinUpdates\WinUpdates.vbs

Y voilá… nuestro script para avisos de updates de Windows listo 🙂

ACTUALIZACIÓN: Un lector del blog me ha hecho saber que aparece un error «Se sobrepasó el final del archivo». Bien, para solucionar esto, os dejo el código del fichero *.htm que debéis guardar como «updatetemplate.htm» en el mismo directorio donde tenéis el resto de los ficheros:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html dir="ltr" lang="EN-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
<meta http-equiv="MSThemeCompatible" content="yes">
<meta name="MS-HAID" content="a_TablDefs">
<meta name="MS.LOCALE" content="EN-US">
<title>Windows updates status report</title>
<meta name="COMPONENT" content="unassigned">
</head>
<style type="text/css">
body { color: #000000;
background: #FFFFFF;
font-size: 80%;
font-family: Verdana, Arial, Sans Serif;
margin-left: 0em;
padding-left: 1.5em; }
a:link, a:visited { color: #0000FF; text-decoration:underline;}

a:active, a:hover { color: #FF0000; cursor:hand; text-decoration:underline;}

a.#reltopics { position:relative; top:1em; }

h1
{ font-size: 125%;
margin-bottom: .5em; clear:both; width: 85%; }

h2
{ font-size: 115%;
margin-top: 1.5em;
margin-bottom: .5em; clear:both; }

h3
{ font-size: 105%;
margin-top: 1.2em;
margin-bottom: .5em; clear:both; }

h4
{ font-size: 105%;
margin-top: 1.2em;
margin-bottom: .5em; clear:both; }

h5
{ font-size: 105%;
margin-top: 1.2em;
margin-bottom: .5em; clear:both; }

p
{ margin-top: .6em;
margin-bottom: .6em; }

ol
{ margin-top: .5em;
margin-bottom: 0em;
margin-left: 2.1em;
padding-left: 0em;
margin-right:2.1em; }

ul
{ margin-top: .6em;
margin-bottom: 0em;
list-style-type: disc;
margin-left: 1.5em;
padding-left: 0em;
margin-right: 1.5em; }

li
{ margin-bottom: .7em; clear:both; }

dd
{ margin-bottom: 0em;
margin-left: 1.5em; }

dt
{ margin-top: 1em; }

pre
{ margin-top: .5em;
margin-bottom: .5em; }

code
{ font-family: Courier New;
font-size: 1.1em; }

table
{ font-size: 100%;
margin-top: 1em;
margin-bottom: 1em; }

th
{ text-align: left;
background: #DDDDDD;
vertical-align: bottom;
padding: .2em;
padding-left: .45em;
padding-right: .5em;}

tr
{ vertical-align: top; }

td
{ background: #EEEEEE;
vertical-align: top;
padding: .2em;
padding-left: .45em;
padding-right: .5em;}
p.indent { margin-left: 1.3em; } /* used to indent text for notes and warnings; use this instead of the blockquote tag */

p.indent2 { margin-left: 3.5em; } /* used to indent text a second level under the first indent */

P.indentGray { color: #666666; } /* used in quad-method procedures */

p.procLabel { font-size: 105%;
font-weight: bold; clear: both; width: 85%; margin-top: 2em; }

p.uatshoot { font-weight: bold; margin-bottom: -.5em; margin-top: 1em; }

table.uatshoot {background-color: #FFFFFF; width: 90%; }

table.uatshoot td { padding: 0px 0px 0px 5px; }
p.note,p.tip, p.important, p.caution, p.warning
{ font-weight:bold; clear:both;
margin-top:1em;
margin-bottom:-.25em; }

</style>

<body>
<h1 class="topicHeading">Windows Update Status Report - [timenow]</h1>
<p><b>[domainname]\[computername]</b></p>
<br>
<!--CriticalStart-->
<div ID=Critical>
<p><b>Critical Updates</b></p>
<table>
<tbody>
<tr>
<th width="20%">Title</th>
<th width="70%">Description</th>
<th width="10%">Links</th>
</tr>

[criticalupdatecontents]

</tbody>
</table>
</div>
<!--CriticalEnd-->
<!--ImportantStart-->
<div ID=Important>
<p><b>Important Updates</b></p>
<table>
<tbody>
<tr>
<th width="20%">Title</th>
<th width="70%">Description</th>
<th width="10%">Links</th>
</tr>

[importantupdatecontents]

</tbody>
</table>
</div>
<!--ImportantEnd-->
<!--ModerateStart-->
<div ID=Moderate>
<p><b>Moderate Priority Updates</b></p>
<table>
<tbody>
<tr>
<th width="20%">Title</th>
<th width="70%">Description</th>
<th width="10%">Links</th>
</tr>

[moderateupdatecontents]

</tbody>
</table>
</div>
<!--ModerateEnd-->
<!--LowStart-->
<div ID=Low>
<p><b>Low Priority Updates</b></p>
<table>
<tbody>
<tr>
<th width="20%">Title</th>
<th width="70%">Description</th>
<th width="10%">Links</th>
</tr>

[lowupdatecontents]

</tbody>
</table>
</div>
<!--LowEnd-->

<!--OthersStart-->
<div ID=Others>
<p><b>Others Priority Updates</b></p>
<table>
<tbody>
<tr>
<th width="20%">Title</th>
<th width="70%">Description</th>
<th width="10%">Links</th>
</tr>

[othersupdatecontents]

</tbody>
</table>
</div>
<!--OthersEnd-->

</body>
</html>

Hecho esto, ya no os debería dar problemas.

Fuente | Microsoft Script Center