Docker ps – objetizando las respuestas

El titulo alternativo era “Dale docker, devolveme un objeto en vez de un montón de caracteres con un formato horrible, estamos en 2019!” pero me parecía demasiado largo.

Este ultimo tiempo estuve intentando migrar a un rol un poco mas divertido, mas dinámico, a lo que creo que es el siguiente paso para un administrador. DevOps.
La palabra resuena mucho por todos lados, todas las empresas quieren dinamismo, escalabilidad, resiliencia, o minimamente poder decir que tienen un equipo “cloud”.

Pero hoy no vengo a hablar de DevOps, ni de corporaciones, ni de laburo. Hoy me desperté con ganas de compartir una cosita muy muy chiquita que me llevó unas cuantas horas para armar (y probar, y putear, y aprender), que probablemente pueda ayudar a cualquier otra persona que empiece a meterse en estas tecnologías tan divertidas y novedosas

Cuestión que estaba haciendo un cursito de Docker en udemy, levantando containers a lo pavote, redirigiendo puertos, apuntando storage, un quilombo bárbaro. Súper súper divertido, todo muy dinámico, hasta que quise ordenar el output de docker ps -a y me di cuenta de lo malacostumbrado que me tiene Powershell.

horrorosos strings 🙁

Cuando uso cualquier cmdlet (get-sarasa) recibo un objeto como respuesta. Un hermoso objeto con propiedades, que puedo usar para filtrar, para ordenar, para listar, para re-usar, todo de manera ordenada, bella y hermosa <3.
Lamentablemente con docker no es el caso 🙁
Como toda herramienta con base linuxera, la respuesta viene en strings, cadenas de caracteres sin inteligencia propia, que un administrador experimentado probablemente sepa parsear muy fácil y rápido con algún comandito sencillo y 100% desconocido para mi (porque soy un windows boy y no tengo los huevos para migrar por ahora) ASI QUE se me ocurrió armar una función sencillita y rápida para dejar de sufrir y poder matar containers sin tener que escribir mucho 😀

Asi que les presento ConvertFrom-DockerPS.

function ConvertFrom-DockerPS {
<#
.SYNOPSIS
Parses docker ps output to make it more readable
.DESCRIPTION
Pipe the results of Docker ps to this function and get the results in a nice, more powershelly format
.PARAMETER
.EXAMPLE
docker ps | ConvertFrom-DockerPS | select names, image, "container id", created # Get all running containers
docker ps -a | ConvertFrom-DockerPS | where {$_.status -like "exited*"} | docker # Get all closed containers
docker ps -a | ConvertFrom-DockerPS | where {$_.status -like "exited*"} | foreach { docker rm $_.'CONTAINER ID' } # remove all stopped containers
.NOTES
The core of the code was taken from https://www.reddit.com/r/PowerShell/comments/8p09mb/how_to_loop_through_docker_ps_a_with_powershell/ so
credits go to reddit user /u/Lee_Dailey (https://www.reddit.com/user/Lee_Dailey/), a great contributor on the powershell subreddit [grin]
I've also (forcefully) learned the how's and why's of using advanced functions, and the massive value of the ISE (which i will never use again, heh)
Thank you fellow admin for reading this ramblings, and party on!
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]$adentro
)
begin{
#Declare the $final helper object
$final=@()
}
process{
$InStuff = $adentro.Split("`n").Trim("`r")
$OutStuff = $InStuff -replace '\s{23,}', ',,' -replace '\s{2,}', ','
#Added this to mitigate an issue with formatting on the ugly header
if ($OutStuff -like "*IMAGE,COMMAND,*") { $OutStuff= $OutStuff.replace(',,',',')}
$final+=$OutStuff
}
end{
#Convert it all to CSV and send it back
$final | ConvertFrom-Csv
}
}
Bello bello objeto 😀

Y ahora los dejo, que estoy súper manija con este curso, si tienen la oportunidad chusmeenlo, que esta muy bien explicado y tiene ejercicios concretos, una plataforma online para poder hacer (casi) todo sin instalar nada, y honestamente creo que el futuro de la administración de sistemas viene por este lado.

Hasta la próxima!

Tenemos PowerShell 5.0!

Finalmente, después de mil vueltas, los muchachos del equipo de PowerShell largaron el Windows Management Framework 5.0, que incluye Powershell 5, PowerShell Desired State Configuration (DSC), Windows Remote Management (WinRM) y Windows Management Instrumentation (WMI).

Continua leyendo “Tenemos PowerShell 5.0!” »

Que hay de nuevo en Powershell 5.0

Hola de nuevo!

Ayer les adelanté la salida de Powershell 5.0. Hoy vengo a comentarles algunas de las novedades más interesantes que nos va a traer esta actualización.

Entre ellas:

  • Write-Information, un cmdlet nuevo que permite especificar como Powershell maneja la información estructurada en un comando en particular.
    Format-Table ahora alinea las columnas automáticamente evaluando los primeros 300ms de datos que recibe.
  • ConverFrom-String, un cmdlet nuevo usado para extraer y parsear estructuras de objetos basándose en contenidos de texto (Excelente para usar con comandos legacy, por ejemplo ipconfig).
  • Un nuevo módulo Microsoft.Powershell.Archive, el cual incluye nuevos cmdlets que permiten comprimir , extraer y modificar archivos y carpetas en ZIPs.
  • Un nuevo módulo, PackageManagement, que permite descubrir e instalar paquetes de software de internet (Como un apt-get para Windows :D).
  • Actualizaciones sobre los cmdlets New-Item, Remove-Item y Get-ChildItem para soportar la creación y administración de SymLinks. Por fin!
  • Get-ChildItem ahora soporta un nuevo parámetro, -Depth, con el cual podremos especificar qué tan profundo queremos que vaya la recursividad (con -Recurse).
  • Get-Clipboard y Set-Clipboard se agregaron al módulo Microsoft.Powershell.Utility, para mejorar el soporte de transferencia de contenido de y hacia las sesiones de Powershell. Estos cmdlets soportan imágenes, archivos de audio, listas de archivos y texto.
  • Clear-RecycleBin se agregó al módulo Microsoft.Powershell.Management, y permite vaciar la papelera de reciclaje (posta? no se me había ocurrido) de cualquier disco, sea interno o externo.
  • New-TemporaryFile, permite crear un archivo temporal (por defecto en %userprofile%\AppData\Local\Temp) perfecto para scripting.
  • La consola de Powershell ahora usa coloreo de sintaxis, como en el ISE (Integrated Scripting Environment)
  • Y, obviamente, el valor de $PSVersionTable.PSVersion se actualizo a 5.0

 

En cuanto salga el instalador oficial prometo hacer un update con el paso a paso (y los datos a tener en cuenta) para poder actualizar y empezar a disfrutar de todas estas funcionalidades nuevas.

Para chequear todas las novedades, podes revisar este link de TechNet

 

Gracias por visitar!

 

Se viene se viene…

Confirmado para finales de febrero: Poweshell 5.0!

En unos dias voy a armar la introduccion completa, con prerequisitos, instrucciones paso a paso para instalar, y las cositas nuevas que trae para hacernos la vida mas facil.
Por ahora eso es todo, nos vemos!

Como actualizar a PowerShell 4.0

Hoy vengo a mostrarles como actualizar a PowerShell 4.0

En esta ocasion voy a usar Windows Server 2008r2 como maquina de prueba.
Voy a verificar la version de PowerShell instalada mostrando la variable $PSVersionTable

$PSVersionTable
$PSVersionTable

Bien, ahora necesito saber si mi sistema operativo es de 32 o 64 bits. Para verificar esto, voy a revisar la variable $env:PROCESSOR_ARCHITECTURE

Continua leyendo “Como actualizar a PowerShell 4.0” »

Buscar pertenencia de grupos en un ciertos usuarios

Entre los miles (?) de pedidos que recibo diariamente, me tocó algo que crei interesante compartir.

Me pidieron un reporte que incluyera los grupos a los que pertenecen una lista de usuarios. OBVIAMENTE no lo iba a hacer a mano, asi que…

Powershell!

Los puntos a tener en cuenta son los siguientes:

  1. c:\datos es la ruta de donde, por defecto, se toma el archivo de usuarios y se guarda el reporte.
  2. lista de usuarios.txt debe contener la lista de usuarios.
  3. En caso de que se quieran seleccionar usuarios con algun filtro, se puede usar get-aduser -filter {enabled -eq $true} (Para usuarios habilitados, por ejemplo)
  4. Devuelve un csv con el nombre “Reporte Usuarios”.
## Agregar el modulo de Active Directory.
import-module activedirectory
$Final=@()
## Apunta a la ruta donde esta el archivo
$ruta="c:\datos"
## Apunta al archivo
$archivo="lista de usuarios.txt"
## Obtiene una lista de todos los usuarios que esten habiliados.
$usuariostotal= get-content $ruta\$archivo
foreach ($usuario in $usuariostotal){
## Guarda el nombre del usuario.
$nombre=$usuario.name
## Guarda el username.
$user=$usuario.samaccountname
$Completo=@()
$Grupos="ERROR DESCONOCIDO"
$primero=$true
$agregar=""
## Busca los grupos a los que pertenece el usuario.
$Completo=get-ADPrincipalGroupMembership $user | select-object name
$cantidad=$Completo.count
## Por cada grupo hace una iteracion.
for($a=0;$a -lt ($cantidad);$a++){
if ($primero){
$primero=$false
## Toma el nombre del grupo y lo pasa a una variable.
$agregar=$Completo[$a].name
## Agrega la variable anterior a $grupos para dejarlo todo en una sola variable.
$Grupos="$agregar"
}
else{
$agregar=$Completo[$a].name
$Grupos+=", $agregar"
}
}
$Final+=$usuario | select-object @{Expression={$nombre};Label="Nombre"},@{Expression={$user};Label="Username"}, @{expression={$Grupos};Label="Grupos"},@{expression={$cantidad};Label="Cantidad"}
}
## Exporta el reporte a un CSV
$Final | export-csv $ruta\"ReporteUsuarios.csv"

Esto es todo por hoy!

Como averiguar los administradores locales de los servidores

En este glorioso día (?) me pidieron de superurgencia un reporte que contuviera todos los administradores locales de los servidores que tenemos en uno de nuestros sitios.
Haciendo cuentas en el aire, me di cuenta de que eran mas de 60!

Y ahora… Quien podrá ayudarme? 🙁

OBVIAMENTE POWERSHELL!

Los puntos a tener en cuenta son los siguientes:

  1. $mascara debe ser cambiado a la subred que se desee (por ej, 172.17.*)
  2. Devuelve un csv con el nombre “Reporte admin” y la mascara de subred que se utilice para buscar.
import-module activedirectory
$ruta=get-location
$prueba=@()
$completo=0
## la subred que se quiere buscar
$mascara="172.16.*"
cd "AD:\"
$equipos= Get-ADComputer -Filter {OperatingSystem -like "*server*"} -Properties ipv4address, CanonicalName, operatingsystem | Where-Object {$_.ipv4address -like $mascara}
$total=$equipos.count
foreach ($compu in $equipos){
$nombre=$compu.name
$laip=$compu.ipv4address
$canonico=$compu.canonicalname
$OS=$compu.operatingsystem
$online=$false
$idioma=" "
$admicompleto=@()
$administradores="ERROR DESCONOCIDO"
$sacoadmin=$false
$adminroto=$false
$primero=$true
$agregar=" "
$online = Test-Connection $nombre -Quiet -count 1
if ($online){
## Esta online
$coso=get-wmiobject -ComputerName $nombre win32_operatingsystem
if(!$?){
$idioma="999999"
}else{
$idioma=$coso.oslanguage
}
Switch ($idioma){
"3082" {
#Español Int
$admicompleto= Invoke-Command -ComputerName $nombre -ScriptBlock {net localgroup administradores}
if($?){
$sacoadmin=$true
}else{
$adminroto=$true
}
break
}
"1033" {
#Ingles US
$admicompleto= Invoke-Command -ComputerName $nombre -ScriptBlock {net localgroup administrators}
if($?){
$sacoadmin=$true
}else{
$adminroto=$true
}
break
}
"1046" {
#Portugues bra
$admicompleto= Invoke-Command -ComputerName $nombre -ScriptBlock {net localgroup administradores}
if($?){
$sacoadmin=$true
}else{
$adminroto=$true
}
break
}
"999999" {
# Si dio error en el get idioma
$administradores="ERROR DE WMI"
$sacoadmin=$false
break
}
Default {
# No se encuentra el codigo de idioma
$administradores="NO SE PUDO RESOLVER EL CODIGO $idioma"
$sacoadmin=$false
break
}
}
if($sacoadmin){
## Ejecuto bien el proceso de admin
for($a=6;$a -lt ($admicompleto.count -2);$a++){
if ($primero){
$primero=$false
$agregar=$admicompleto[$a]
$administradores="$agregar"
}
else{
$agregar=$admicompleto[$a]
$administradores+=", $agregar"
}
}
}
if($adminroto){
##no ejecuto el proceso de admin por un error
$administradores="ERROR DE EJECUCION REMOTA"
}
}else{
## No esta online
$idioma="NO DISPONIBLE"
$administradores="NO DISPONIBLE"
}
$PRUEBA+=$compu | select-object @{Expression={$nombre};Label="Nombre"}, @{expression={$online};label="Online"}, @{expression={$laip};label="IP"}, @{expression={$OS};label="OS"}, @{expression={$canonico};label="OU"}, @{Expression={$idioma};Label="Idioma"},@{expression={$sacoadmin};Label="ProcesoAdmins"} ,@{expression={$administradores};Label="Administradores"}
}
cd $ruta
$archivo="./reporte admin "+$mascara.Replace('*','x')+".csv"
$prueba | export-csv $archivo

El resultado no fue 100% exitoso, pero de 60+ servidores, quedaron unos 5-6 en los que tuve que entrar a revisar manualmente. Nada mal, eh?

Prometo mas actualizaciones en las proximas semanas, tengo un par de scripts armados que no tuve tiempo de compartir.

Eso es todo por hoy!

Como migrar un SMTP Relay en Exchange 2013

Como les habré comentado a algunos, estamos en plena migración de sistema de correos (Exchange 2007 a 2013), y como toda migración siempre hay cosas nuevas que probar y aprender 😀

Hoy me toco migrar un Receive Connector que usamos de SMTP Relay (uno en el ambiente viejo) y configurarlo balanceado entre 4 servidores (todos en el ambiente nuevo) Para esto tenía que copiar a mano unos 130 registros. 130 registros. A 4 servidores. NO.

Primero busque una manera rápida de exportar la lista de ips que estaban habilitadas.

SERVIDORBASE es el nombre del server que tiene el Receive Connector del que vamos a copiar la lista de ips.
RC-A-COPIAR es el nombre del Receive Connector del que vamos a copiar esta lista.

$ips=(Get-ReceiveConnector "SERVIDORBASE\RC-A-COPIAR").remoteipranges

Perfecto! Con eso tenemos una array de ips (y rangos de ips, si es que usamos) completamente importables. (Cabe aclarar que lo probé tanto desde un Receive Connector armado en 2007 como en uno armado en 2013. No sé si habrá algún cambio de estructura en cuanto a tipo de dato, pero exportándolo así quedan igualitos)

El paso siguiente fue un poquito más largo. Tenía que crear los RC, configurarlos para hacer Relay y agregarles las ips que ya había exportado.

SERVIDORTARGET es el nombre del server en donde vamos a configurar el RC nuevo.

New-ReceiveConnector -Server SERVIDORTARGET -name "Relay SERVIDORTARGET" -Usage custom -AuthMechanism externalauthoritative -PermissionGroups ExchangeServers -Bindings 0.0.0.0:25 -TransportRole FrontEndTransport -RemoteIPRanges $ips

Pero momento, justo antes de empezar a crear los RC nuevos, se me ocurrió revisar todos los RC que ya existían… Menos mal, porque de 4 servidores, dos ya tenían creados un RC destinado a Relay.
(Asumo que todos son ordenados y que nombran adecuadamente los RC. Si no es así, ordénenlos, no sean giles)

Get-ReceiveConnector | where {$_.name -like "*relay*"}
Oops
Oops

Bien, vamos por partes entonces. Primero usé el código que puse antes (Crear RC nuevos) y armé los dos que no existen. Listo? Bueno, ahora a modificar los que me faltan.
(En mi caso, quería modificar todos los RC cuyo nombre empezara con Relay. Si tuviera que modificar solo un RC lo especificaría en la primer parte del código que pongo más abajo)

Get-ReceiveConnector | where {$_.name -like "relay*"} | Set-ReceiveConnector -RemoteIPRanges $ips

Listo!

Si necesitan verificar que haya quedado bien se pueden ver los datos de cada RC de manera sencilla, como test rápido vamos a revisar que tengamos la misma cantidad de ips habilitadas en todos los RC cuyo nombre comience con Relay.

Get-ReceiveConnector | where {$_.name -like "relay*"} | select name, @{Expression={$_.remoteipranges.count};Label="cantidad"}
Todos

Con esto termina el post de hoy. Hasta la próxima!

Ver usuarios bloqueados

Alguna vez les paso que un usuario de Active Directory se les bloqueo varias veces una misma semana? Y un mismo día?
Bueno, a no preocuparse, con este pequeño script vamos a poder ver en donde se produce ese bloqueo o intento fallido de login para poder resolverlo fácilmente.

Antes que nada, necesitamos tener instaladas las herramientas de administración remota (también llamado RSAT).
Podemos bajarlas desde estos links:
Windows 7 x86
Windows 7 x64

Una vez instalados, solo queda abrir una ventana de Powershell y ejecutar el siguiente script.

function show-progressbar([int]$actual,[int]$completo,[string]$status,[string]$Activity)
{
$porcentaje=($actual/$completo)*100
if (!$status){
$status="Buscando datos $actual de $completo"
}
if (!$Activity){
$Activity="Obteniendo Resultados"
}
Write-Progress -Activity $Activity -status $status -percentComplete $porcentaje
}
###############################################################
# Ya explique esta funcion en este post #
# https://powershelleando.com.ar/2014/05/30/show-progressbar/ #
###############################################################
import-module activedirectory
$logcompleto=@()
$logdcs=@()
$cuentadc=1
# crea una carpeta "Eventos". Si ya existe no hace nada.
New-Item -Name eventos -Type directory -Force
# Levanta todos los DCs
$dcs=Get-ADDomainController -Filter *
$dctotal=$dcs.count
foreach ($dc in $dcs){
$nombredc=$dc.name
# Variable de ayuda para filtrar cuando no hay eventos
$sineventos=$false
$cuentaeventos=0
# Variable de ayuda para filtrar eventos de no repeticion
$mostrar=$true
$tipo=" "
# Revisando Domain Controller $nombredc - Chequeando disponibilidad.
$Status="Revisando Domain Controller $nombredc ($cuentadc de $dctotal)- Chequeando disponibilidad."
show-progressbar $cuentadc $dctotal $status
$online = Test-Connection $nombredc -Quiet -count 1
if ($online){
# Revisando Domain Controller $nombredc - Recuperando eventos.
$Status="Revisando Domain Controller $nombredc ($cuentadc de $dctotal) - Recuperando eventos."
show-progressbar $cuentadc $dctotal $status
# Get-WinEvent busca en el log de eventos, usamos -FilterXPath
#para consultar por eventos especificos, en este caso, indicamos
#los dos EventIDs que nos pueden resultar interesantes.
# EventID=4740 es el evento de bloqueo de usuario
# EventID=4625 es el evento de intento fallido de login
$logs=(Invoke-Command -computername $nombredc -ScriptBlock {Get-WinEvent -FilterXml "<QueryList><Query Path='Security'><Select Path='Security'>*[System[(EventID=4740 or EventID=4625)]]</Select></Query></QueryList>" -ErrorAction silentlyContinue} )
if ($logs -eq $null){
# Marca que no hay eventos para acomodar
$sineventos=$true
$eventostotal=0
}
if (!$sineventos){
$eventostotal=$logs.count
# Revisa evento por evento
foreach ($evento in $logs){
$cuentaeventos+=1
# Revisando Domain Controller $nombredc - cantidad de eventos
$Status="Revisando Domain Controller $nombredc ($cuentadc de $dctotal) - Evento numero $cuentaeventos de $eventostotal"
show-progressbar $cuentadc $dctotal $status
# mensaje tiene la variable message del evento
$mensaje=$evento.message
$hora=$evento.TimeCreated.ToShortTimeString()
$fecha=$evento.TimeCreated.ToShortDateString()
$id=$evento.ID
$datos=$mensaje.Split("`n")
Switch ($id)
{
4740{
## CASE 4740 - Bloqueo de un usuario
# datos[10] tiene la linea del usuario
$usuario=(($datos[10].split("`t"))[3])
# datos[13] tiene la linea del equipo que genero el evento
$Maquina=(($datos[13].split("`t"))[2])
$tipo="Bloqueo"
## FIN CASE 4740
}
4625{
## CASE 4625 - Intento de login fallido
## (chequear por si es un error)
if ($evento.Providername -eq "Microsoft-Windows-Security-Auditing"){
# datos[12] tiene la linea del usuario
$usuario=(($datos[12].split("`t"))[3])
# datos[25] tiene la linea del equipo que
#genero el evento
$Maquina=(($datos[25].split("`t"))[2])
$tipo="Intento de Login"
}
else{
$mostrar=$false
}
## FIN CASE 4625
}
default{
## Hubo un error.
$usuario="ERROR"
$maquina="ERROR"
$tipo="ERROR"
}
}
if ($mostrar){
$logcompleto+= $usuario | select-object @{Expression={$usuario};Label="Usuario"},@{Expression={$maquina};Label="Equipo"},@{Expression={$tipo};Label="Evento"},@{Expression={$fecha};Label="Fecha"},@{Expression={$Hora};Label="Hora"},@{Expression={$nombredc};Label="DC"}
}
}
}
}
else{
# Si el domain controller no esta disponible
write-host "El domain controller $nombredc esta OFFLINE" -backgroundcolor "red" -ForegroundColor black
}
$logDCS+=$cuentadc|select-object @{Expression={$nombredc};Label="DC"}, @{Expression={$online};Label="Online"}, @{Expression={$cuentaeventos};Label="Eventos"}
$cuentadc+=1
}
# Exporta el log de los usuarios bloqueados a CSV
$logcompleto| export-csv ./eventos/Log_Bloqueados.csv
# Exporta el log de estado de los DCs a CSV
$logdcs | export-csv ./eventos/log_dcs.csv
##Fin

Esto fue todo por hoy, si tienen alguna duda, comentario o sugerencia pueden escribirme mas abajo.

Hasta la próxima!

Show-ProgressBar

Hoy voy a mostrar una pequeña función que suelo usar cuando corro algún ciclo largo, mas que nada para poder seguirlo paso a paso mientras se ejecuta.

Los únicos datos que si o si hay que pasarle son los números actual y el total de registros del ciclo que se esta recorriendo.

function show-progressbar([int]$actual,[int]$completo,[string]$status,[string]$Activity)
{
$porcentaje=($actual/$completo)*100
# Si no le pasamos un parametro $Status, pone uno generico
if (!$status){
$status="Buscando datos $actual de $completo"
}
# Si no le pasamos un parametro $Activity, pone uno generico
if (!$Activity){
$Activity="Obteniendo Resultados"
}
Write-Progress -Activity $Activity -status $status -percentComplete $porcentaje
}

En los proximos scripts voy a usar esta pequeña funcion, asi que preferia presentarla primero antes de marear a algun colgado.

Hasta la próxima!