Notas de Powershell
He cambiado a http://grafeno.wordpress.com
martes, 15 de enero de 2013
Cambio de contraseñas locales de administrador
El siguiente script muestra como cambiar la contraseña del administrador local en varios equipos a la vez:
$erroractionpreference = "SilentlyContinue"
$Equipos=@("PC1","PC2","localhost")
foreach ($equipo in $Equipos)
{
Test-Connection $equipo -Count 1 >$null
if ($? -eq $true)
{
$admin=[adsi]("WinNT://" + $equipo + "/administrador, user")
$admin.psbase.invoke("SetPassword", "micontraseña")
if ($? -eq $true){ Write-Host "Equipo $equipo. Contraseña cambiada." -Foreground Green}
else { Write-Host "Equipo $equipo. No se ha podido cambiar la contraseña." -Foreground Red}
}
else{ Write-Host "Equipo $equipo no accesible" -Foreground Red }
}
$erroractionpreference = "SilentlyContinue"
$Equipos=@("PC1","PC2","localhost")
foreach ($equipo in $Equipos)
{
Test-Connection $equipo -Count 1 >$null
if ($? -eq $true)
{
$admin=[adsi]("WinNT://" + $equipo + "/administrador, user")
$admin.psbase.invoke("SetPassword", "micontraseña")
if ($? -eq $true){ Write-Host "Equipo $equipo. Contraseña cambiada." -Foreground Green}
else { Write-Host "Equipo $equipo. No se ha podido cambiar la contraseña." -Foreground Red}
}
else{ Write-Host "Equipo $equipo no accesible" -Foreground Red }
}
viernes, 5 de noviembre de 2010
PowerShell y el Framework .NET
PowerShell se apoya en Framework.NET. ¿Qué significa esto? Sencillamente que PowerShell se convierte en un lenguaje más de programación como C# o Visual Basic. Aunque PowerShell está pensado como lenguaje de scripting, podemos hacer lo mismo que con cualquier otro lenguaje de programación .NET.
Ejemplo. PowerShell no incluye cmdlets para realizar cálculos matemáticos complejos, pero Framework.NET sí. En un programa hecho en C# podemos acceder a las funciones matemáticas incluyendo el espacio de nombres System.Math ubicado en la DLL mscorlib.dll.
En PowerShell esto lo hacemos con el objeto [System.Math]
Necesito información de métodos y propiedades de [System.Math] para saber qué puedo usar: [System.Math] | gm
Vemos que hay un método llamado GetMembers(). Con esto ya podemos consultar a la clase:
[System.Math].GetMembers()
Y hacer uso de todos los métodos y propiedades estáticas(no hace falta instanciar un objeto):
[System.Math]::PI
[System.Math]::sqrt(25)
Y lo mismo con cualquier otra clase de FrameWork.NET. ¿Os suena esto?:
[System.Console]::WriteLine("Hola Mundo")
¿Y si quiero hacer uso de métodos o propiedades no estáticos? Pues a crear un objeto.
$Ping = New-Object System.Net.NetworkInformation.Ping
$Ping.Send("10.10.10.10")
¿Y si la DLL con las clases que quiero usar no está cargada?
Para ver ensamblados cargados: [System.AppDomain]::CurrentDomain.GetAssemblies()
o mejor
[System.AppDomain]::CurrentDomain.GetAssemblies() | foreach-object { split-path $_.Location -leaf } | sort
Para cargar un ensamblado:
Add-Type -AssemblyName Nombre_del_ensamblado (si está en la GAC)
Add-Type -Path "ruta_completa_al_ensamblado"
Ejemplo. PowerShell no incluye cmdlets para realizar cálculos matemáticos complejos, pero Framework.NET sí. En un programa hecho en C# podemos acceder a las funciones matemáticas incluyendo el espacio de nombres System.Math ubicado en la DLL mscorlib.dll.
En PowerShell esto lo hacemos con el objeto [System.Math]
Necesito información de métodos y propiedades de [System.Math] para saber qué puedo usar: [System.Math] | gm
Vemos que hay un método llamado GetMembers(). Con esto ya podemos consultar a la clase:
[System.Math].GetMembers()
Y hacer uso de todos los métodos y propiedades estáticas(no hace falta instanciar un objeto):
[System.Math]::PI
[System.Math]::sqrt(25)
Y lo mismo con cualquier otra clase de FrameWork.NET. ¿Os suena esto?:
[System.Console]::WriteLine("Hola Mundo")
¿Y si quiero hacer uso de métodos o propiedades no estáticos? Pues a crear un objeto.
$Ping = New-Object System.Net.NetworkInformation.Ping
$Ping.Send("10.10.10.10")
¿Y si la DLL con las clases que quiero usar no está cargada?
Para ver ensamblados cargados: [System.AppDomain]::CurrentDomain.GetAssemblies()
o mejor
[System.AppDomain]::CurrentDomain.GetAssemblies() | foreach-object { split-path $_.Location -leaf } | sort
Para cargar un ensamblado:
Add-Type -AssemblyName Nombre_del_ensamblado (si está en la GAC)
Add-Type -Path "ruta_completa_al_ensamblado"
Funciones
En PowerShell podemos hacer uso de funciones para evitar la repetición de un conjunto de instrucciones continuamente. Un ejemplo de función es:
Function salida
{
Write-Host "Mensaje"
}
A una función le podemos pasar valores y hay tres maneras de hacerlo.
Primera manera: Podemos pasar a una función todos los parámetros que queramos y hacemos referencia a ellos mediante $args[n]
Function suma
{
[int]$args[0] + [int]$args[1]
}
A esta función la podemos llamar así:
suma 5 10
Segunda manera: Podemos pasar a una función una serie de parámetros especificados
Function suma ([int]$x, [int]$y)
{
$x+$y
}
A esta función le llamamos de una de las siguientes maneras:
suma 5 10
suma -x 5 -y 10
Tercera manera:
Function suma
{
Param ([int]$x, [int]$y)
$x+$y
}
A esta función le llamamos de una de las siguientes maneras:
suma 5 10
suma -x 5 -y 10
Las funciones en PowerShell devuelven cualquier valor que se envíe al stream de salida. En los ejemplos anteriores solo se devuelve un valor, pero no tenemos límite en el número de valores devueltos, todos se van agregando a un array.
Nota. Podemos usar la palabra clave Return para salir inmediatamente de una función devolviendo un único valor.
Function salida
{
Write-Host "Mensaje"
}
A una función le podemos pasar valores y hay tres maneras de hacerlo.
Primera manera: Podemos pasar a una función todos los parámetros que queramos y hacemos referencia a ellos mediante $args[n]
Function suma
{
[int]$args[0] + [int]$args[1]
}
A esta función la podemos llamar así:
suma 5 10
Segunda manera: Podemos pasar a una función una serie de parámetros especificados
Function suma ([int]$x, [int]$y)
{
$x+$y
}
A esta función le llamamos de una de las siguientes maneras:
suma 5 10
suma -x 5 -y 10
Tercera manera:
Function suma
{
Param ([int]$x, [int]$y)
$x+$y
}
A esta función le llamamos de una de las siguientes maneras:
suma 5 10
suma -x 5 -y 10
Las funciones en PowerShell devuelven cualquier valor que se envíe al stream de salida. En los ejemplos anteriores solo se devuelve un valor, pero no tenemos límite en el número de valores devueltos, todos se van agregando a un array.
Nota. Podemos usar la palabra clave Return para salir inmediatamente de una función devolviendo un único valor.
Versión del sistema operativo
Si en un script de PowerShell necesitamos conocer la versión del sistema operativo para realizar tareas diferentes dependiendo de dicha versión, lo podemos hacer de la siguiente manera:
$maquina = $env:computername
$filtro = "(&(objectCategory=Computer)(Name=$maquina))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtro
$objPath = $objSearcher.FindOne()
$objEquipo = $objPath.GetDirectoryEntry()
$objEquipo.operatingSystem
$maquina = $env:computername
$filtro = "(&(objectCategory=Computer)(Name=$maquina))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtro
$objPath = $objSearcher.FindOne()
$objEquipo = $objPath.GetDirectoryEntry()
$objEquipo.operatingSystem
Mapeo de impresoras
El siguiente ejemplo, muestra como mapear impresoras de red con un script de PowerShell basándose en el departamento al que pertenecen los usuarios.
Condiciones:
1) Los usuarios del Directorio Activo tienen que tener el atributo department rellenado con el departamento al que pertenecen.
2) Las impresoras tienen que estar publicadas en el Directorio Activo y el atributo location tiene que contener un nombre de departamento.
El script recorre todas las impresoras y mapea al usuario las impresoras cuyo atributo location coincide con el atributo department del usuario.
###Función que mapea la impresora
function mapear_impresora([string]$ruta)
{
$net = New-Object -com WScript.Network;
$impresoras=$net.EnumPrinterConnections()
if ($impresoras -like $ruta) {$net.RemovePrinterConnection($ruta,$true)}
$net.AddWindowsPrinterConnection($ruta)
}
#Obtenemos el usuario que inicia la sesión.
$usuario = $env:username
$filtro = "(&(objectCategory=User)(samAccountName=$usuario))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtro
$objPath = $objSearcher.FindOne()
$objUser = $objPath.GetDirectoryEntry()
###Obtenemos las impresoras del Directorio Activo
$filtroimpresora = "(&(objectCategory=PrintQueue))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtroimpresora
$objPath2=$objSearcher.FindAll()
###Mapeos por usuario
for($n=0;$n -lt $objPath2.Count;$n++) { if ( $objPath2[$n].Properties.location -eq $objUser.Properties.department ) { mapear_impresora -ruta $objPath2[$n].Properties.uncname } }
Este script se puede asociar a una directiva de grupo como script de inicio de los usuarios.
Condiciones:
1) Los usuarios del Directorio Activo tienen que tener el atributo department rellenado con el departamento al que pertenecen.
2) Las impresoras tienen que estar publicadas en el Directorio Activo y el atributo location tiene que contener un nombre de departamento.
El script recorre todas las impresoras y mapea al usuario las impresoras cuyo atributo location coincide con el atributo department del usuario.
###Función que mapea la impresora
function mapear_impresora([string]$ruta)
{
$net = New-Object -com WScript.Network;
$impresoras=$net.EnumPrinterConnections()
if ($impresoras -like $ruta) {$net.RemovePrinterConnection($ruta,$true)}
$net.AddWindowsPrinterConnection($ruta)
}
#Obtenemos el usuario que inicia la sesión.
$usuario = $env:username
$filtro = "(&(objectCategory=User)(samAccountName=$usuario))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtro
$objPath = $objSearcher.FindOne()
$objUser = $objPath.GetDirectoryEntry()
###Obtenemos las impresoras del Directorio Activo
$filtroimpresora = "(&(objectCategory=PrintQueue))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtroimpresora
$objPath2=$objSearcher.FindAll()
###Mapeos por usuario
for($n=0;$n -lt $objPath2.Count;$n++) { if ( $objPath2[$n].Properties.location -eq $objUser.Properties.department ) { mapear_impresora -ruta $objPath2[$n].Properties.uncname } }
Este script se puede asociar a una directiva de grupo como script de inicio de los usuarios.
Mapeos unidades de red
PowerShell incluye varios cmdlet para la gestión de unidades ya sean locales o de red. son los cmdlets con nombre PSDrive y verbos Get, New y Remove. El problema de estos cmdlets es que solo actúan en las sesiones de PowerShell, es decir, si creamos un mapeo de red desde PowerShell con el cmdlet New-Psdrive, la unidad mapeada solo es visible en la consola de PowerShell, y no en el explorador de Windows, por ejemplo. Por lo que tenemos que hacer uso de objetos COM para el mapeo correcto de las unidades de red.
El siguiente ejemplo mapea unidades de red a un usuario basándose en la pertenencia a determinados grupos:
#Función que mapea la unidad de red.
function mapear_unidad([string]$unidad, [string]$ruta)
{
$net = New-Object -com WScript.Network;
$unidades=$net.EnumNetworkDrives();
if ($unidades -like $unidad) { $net.removenetworkdrive($unidad,$true)}
$net.mapnetworkdrive($unidad,$ruta)
}
#Obtenemos el usuario que inicia la sesión.
$usuario = $env:username
$filtro = "(&(objectCategory=User)(samAccountName=$usuario))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtro
$objPath = $objSearcher.FindOne()
$objUser = $objPath.GetDirectoryEntry()
#Obtenemos los grupos a los que pertenece.
$groups=$objUser.memberOf
#Los grupos están en formato LDAP, vamos a quedarnos con el nombre CN
function new-array {,$args}
$groupCN= new-array
foreach($i in $groups)
{ $groupCN+=($i.split(",")[0]).split("=")[1] }
#Mapeo de unidad personal
$carpetausuario=$objUser.sAMAccountName
mapear_unidad -unidad "K:" -ruta "\\Servidor\Personales\$carpetausuario"
#Mapeo de unidades de grupo
foreach ($group in $groupCN)
{
switch ($group)
{
Grupo1 { mapear_unidad -unidad "T:" -ruta "\\Servidor\CarpetaGrupo1" }
Grupo2 { mapear_unidad -unidad "M:" -ruta "\\Servidor\CarpetaGrupo2" }
default {"No hay mapeos para el grupo $group"}
}
}
El siguiente ejemplo mapea unidades de red a un usuario basándose en la pertenencia a determinados grupos:
#Función que mapea la unidad de red.
function mapear_unidad([string]$unidad, [string]$ruta)
{
$net = New-Object -com WScript.Network;
$unidades=$net.EnumNetworkDrives();
if ($unidades -like $unidad) { $net.removenetworkdrive($unidad,$true)}
$net.mapnetworkdrive($unidad,$ruta)
}
#Obtenemos el usuario que inicia la sesión.
$usuario = $env:username
$filtro = "(&(objectCategory=User)(samAccountName=$usuario))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $filtro
$objPath = $objSearcher.FindOne()
$objUser = $objPath.GetDirectoryEntry()
#Obtenemos los grupos a los que pertenece.
$groups=$objUser.memberOf
#Los grupos están en formato LDAP, vamos a quedarnos con el nombre CN
function new-array {,$args}
$groupCN= new-array
foreach($i in $groups)
{ $groupCN+=($i.split(",")[0]).split("=")[1] }
#Mapeo de unidad personal
$carpetausuario=$objUser.sAMAccountName
mapear_unidad -unidad "K:" -ruta "\\Servidor\Personales\$carpetausuario"
#Mapeo de unidades de grupo
foreach ($group in $groupCN)
{
switch ($group)
{
Grupo1 { mapear_unidad -unidad "T:" -ruta "\\Servidor\CarpetaGrupo1" }
Grupo2 { mapear_unidad -unidad "M:" -ruta "\\Servidor\CarpetaGrupo2" }
default {"No hay mapeos para el grupo $group"}
}
}
jueves, 4 de noviembre de 2010
Logon Scripts con PowerShell
Es habitual tener scripts de inicio de sesión programados en Batch o en Visual Basic Script. Ahora es posible hacerlo también con PowerShell.
Cosas a tener en cuenta:
1) Los scripts de inicio van a estar en una carpeta compartida de un controlador de dominio y las máquinas del dominio acceden al script a través de la red. Es muy importante que las máquinas cliente reconozcan el acceso al DC como si fuera un acceso de Intranet, no un acceso de Internet, ya que entonces saltará el famoso aviso de "Advertencia de seguridad de Abrir archivo" y el script no llegaría a ejecutarse. (Ajustar los sitios de intranet en las propiedades de Internet Explorer, agregando rutas como file://Nombre_DC)
2) Los scripts de PowerShell son archivos con extensión ps1. EStos archivos tienen como acción predeterminada "Abrir" en lugar de "Ejecutar", por lo que el usuario, al iniciar la sesión, se encontrará con el notepad abierto con el código del script en lugar de ejecutarse. La solución es acceder al registro en todas las máquinas cliente:
HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell el valor predeterminado es Open, debemos cambiarlo a 0, que es la acción asociada a ejecutar el script usando el intérprete PowerShell.
3) Lógicamente, PowerShell tiene que estar instalado. (En Windows XP se puede instalar a través de las actualizaciones automáticas o mediante una directiva de grupo en la que asociamos como Script de inicio de máquina el ejecutable: WindowsXP-KB968930-x86-ESN.exe con parámetros /quiet /passive )
Una vez establecidas estas 3 condiciones, podemos crear directivas de grupo que tengan asociados scripts de inicio de sesión realizados con PowerShell.
Cosas a tener en cuenta:
1) Los scripts de inicio van a estar en una carpeta compartida de un controlador de dominio y las máquinas del dominio acceden al script a través de la red. Es muy importante que las máquinas cliente reconozcan el acceso al DC como si fuera un acceso de Intranet, no un acceso de Internet, ya que entonces saltará el famoso aviso de "Advertencia de seguridad de Abrir archivo" y el script no llegaría a ejecutarse. (Ajustar los sitios de intranet en las propiedades de Internet Explorer, agregando rutas como file://Nombre_DC)
2) Los scripts de PowerShell son archivos con extensión ps1. EStos archivos tienen como acción predeterminada "Abrir" en lugar de "Ejecutar", por lo que el usuario, al iniciar la sesión, se encontrará con el notepad abierto con el código del script en lugar de ejecutarse. La solución es acceder al registro en todas las máquinas cliente:
HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell el valor predeterminado es Open, debemos cambiarlo a 0, que es la acción asociada a ejecutar el script usando el intérprete PowerShell.
3) Lógicamente, PowerShell tiene que estar instalado. (En Windows XP se puede instalar a través de las actualizaciones automáticas o mediante una directiva de grupo en la que asociamos como Script de inicio de máquina el ejecutable: WindowsXP-KB968930-x86-ESN.exe con parámetros /quiet /passive )
Una vez establecidas estas 3 condiciones, podemos crear directivas de grupo que tengan asociados scripts de inicio de sesión realizados con PowerShell.
Propiedad useraccountcontrol
Esta propiedad de los usuario del Directorio Activo contiene información a nivel de bit. A continuación muestro algunos bits de los más útiles (de bit menos significativo a más significativo):
ACCOUNTDISABLE Segundo bit puesto a 1 indica cuenta deshabilitada.
LOCKOUT Cuarto bit puesto a 1 indica cuenta bloqueada.
PASSWD_CANT_CHANGE Sexto bit puesto a 1 indica que no puede cambiar la contraseña.
NORMAL_ACCOUNT Décimo bit puesto a 1 indica cuenta normal.
DONT_EXPIRE_PASSWORD Décimoseptimo bit puesto a 1 indica que la contraseña no caduca.
PASSWORD_EXPIRED Vigésimo tercero bit puesto a uno indica que la contraseña caduca.
Para saber si un determinado bit está puesto a uno o a cero, por ejemplo para el bit número 17:
$numero -band 0x10000
Si el resultado es distinto de cero, el bit número 17 está puesto a 1. Si el resultado es cero, entonces el bit está puesto a cero.
Para el ejemplo de hace dos posts, podemos hacer:
$resultado=$($informacion.Properties.useraccountcontrol) -band 0x10000
if ($resultado -ne 0) { Write-Host " La contraseña no tiene fecha de caducidad"}
ACCOUNTDISABLE Segundo bit puesto a 1 indica cuenta deshabilitada.
LOCKOUT Cuarto bit puesto a 1 indica cuenta bloqueada.
PASSWD_CANT_CHANGE Sexto bit puesto a 1 indica que no puede cambiar la contraseña.
NORMAL_ACCOUNT Décimo bit puesto a 1 indica cuenta normal.
DONT_EXPIRE_PASSWORD Décimoseptimo bit puesto a 1 indica que la contraseña no caduca.
PASSWORD_EXPIRED Vigésimo tercero bit puesto a uno indica que la contraseña caduca.
Para saber si un determinado bit está puesto a uno o a cero, por ejemplo para el bit número 17:
$numero -band 0x10000
Si el resultado es distinto de cero, el bit número 17 está puesto a 1. Si el resultado es cero, entonces el bit está puesto a cero.
Para el ejemplo de hace dos posts, podemos hacer:
$resultado=$($informacion.Properties.useraccountcontrol) -band 0x10000
if ($resultado -ne 0) { Write-Host " La contraseña no tiene fecha de caducidad"}
Suscribirse a:
Entradas (Atom)