PowerShell permite trabajar con bibliotecas DLL de .NET para reutilizar código, consultar clases, inspeccionar métodos y ejecutar funcionalidades implementadas en C# sin necesidad de abrir Visual Studio.
En este tutorial veremos cómo crear una biblioteca de clases, compilarla desde PowerShell, cargarla con Add-Type o Reflection.Assembly, consultar los tipos exportados y ejecutar métodos estáticos y de instancia.
También aprenderemos a inspeccionar los parámetros de los métodos, invocar funciones mediante reflexión y solucionar los errores más habituales.
1. ¿Qué es una DLL?
Una DLL (Dynamic Link Library) es una biblioteca que contiene código y recursos que pueden utilizar otros programas. En el ecosistema .NET, una DLL puede ser un ensamblado que incluye clases, métodos, interfaces y otros tipos reutilizables.
Por ejemplo, una biblioteca puede contener una clase con operaciones matemáticas:
namespace MiBiblioteca
{
public class Utilidades
{
public int Sumar(int a, int b)
{
return a + b;
}
}
}
Una aplicación compatible puede cargar esa biblioteca y utilizar sus métodos sin tener que incorporar el código fuente original.
En PowerShell podemos trabajar con DLL de .NET para:
- Reutilizar funciones implementadas en C#.
- Cargar bibliotecas propias o de terceros.
- Consultar los tipos públicos que contiene un ensamblado.
- Inspeccionar los métodos de una clase.
- Ejecutar métodos estáticos y de instancia.
- Consultar parámetros y tipos de retorno.
- Automatizar operaciones mediante código .NET.
Es importante tener en cuenta que no todas las DLL son iguales. Una biblioteca nativa de Windows no se puede utilizar directamente como si fuera una biblioteca administrada de .NET.
2. Requisitos previos
Para seguir los ejemplos utilizaremos Windows PowerShell 5.1 y el compilador de C# de .NET Framework.
Abre PowerShell y consulta la versión:
$PSVersionTable
Para comprobar específicamente la versión principal:
$PSVersionTable.PSVersion
Si aparece la versión 5.1, puedes continuar con los procedimientos basados en Windows PowerShell.
Comprobar el compilador de C#
En muchas instalaciones de Windows con .NET Framework están disponibles estas rutas:
Test-Path "$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
Para comprobar también la versión de 32 bits:
Test-Path "$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe"
Si alguna devuelve True, puedes utilizar el compilador correspondiente.
Si ambas devuelven False, tendrás que utilizar otro compilador disponible o instalar el entorno de desarrollo de .NET adecuado.
3. Crear la carpeta del proyecto
Vamos a crear un directorio para guardar el código fuente y la biblioteca compilada.
New-Item -Path "C:\MiDLL" -ItemType Directory -Force
Set-Location "C:\MiDLL"
La estructura inicial será:
C:\MiDLL\
Dentro de esta carpeta guardaremos el archivo Utilidades.cs y generaremos MiBiblioteca.dll.
4. Crear el código fuente en C#
El primer paso consiste en crear una clase con varios métodos que después podremos utilizar desde PowerShell.
Ejecuta el siguiente bloque:
$codigo = @'
namespace MiBiblioteca
{
public class Utilidades
{
public string Saludar(string nombre)
{
return "Hola, " + nombre;
}
public int Sumar(int a, int b)
{
return a + b;
}
public int Multiplicar(int a, int b)
{
return a * b;
}
public static string ObtenerVersion()
{
return "1.0.0";
}
}
}
'@
Set-Content
-Path "C:\MiDLL\Utilidades.cs"
-Value $codigo
-Encoding UTF8
El archivo contiene la clase Utilidades, dentro del espacio de nombres MiBiblioteca.
Los métodos tienen distintas características:
| Método | Tipo | Función |
|---|---|---|
Saludar | Instancia | Devuelve un saludo personalizado. |
Sumar | Instancia | Suma dos números. |
Multiplicar | Instancia | Multiplica dos números. |
ObtenerVersion | Estático | Devuelve la versión de la biblioteca. |
Los métodos de instancia requieren crear un objeto. Los métodos estáticos se pueden invocar directamente sobre la clase.
5. Compilar la DLL desde PowerShell
Ahora vamos a convertir el archivo Utilidades.cs en una biblioteca DLL.
5.1. Localizar el compilador
Ejecuta:
$candidatos = @(
"$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe",
"$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe"
)
$csc = $candidatos |
Where-Object { Test-Path $_ } |
Select-Object -First 1
if (-not $csc) {
throw "No se encuentra csc.exe. Comprueba la instalación de .NET Framework."
}
Write-Host "Compilador encontrado: $csc"
El script busca primero el compilador de 64 bits y después el de 32 bits.
5.2. Generar la biblioteca
Una vez localizado el compilador, ejecuta:
& $csc/nologo/target:library/out:"C:\MiDLL\MiBiblioteca.dll""C:\MiDLL\Utilidades.cs" if ($LASTEXITCODE -ne 0) { throw "La compilación ha fallado." } Write-Host "DLL compilada correctamente."
Los parámetros principales son:
/nologo: evita mostrar la información inicial del compilador./target:library: genera una biblioteca DLL./out:: especifica la ruta y el nombre del archivo de salida.
Si la compilación se completa correctamente, se generará C:\MiDLL\MiBiblioteca.dll.
5.3. Comprobar el resultado
Get-Item "C:\MiDLL\MiBiblioteca.dll" |
Select-Object Name, Length, FullName, LastWriteTime
También puedes verificar que existe:
Test-Path "C:\MiDLL\MiBiblioteca.dll"
El resultado esperado es:
True
La carpeta contendrá los siguientes archivos:
C:\MiDLL\
├── Utilidades.cs
└── MiBiblioteca.dll
6. Cargar la DLL con Add-Type
Una de las formas más sencillas de cargar una biblioteca .NET en PowerShell consiste en utilizar Add-Type.
Ejecuta:
Add-Type -Path "C:\MiDLL\MiBiblioteca.dll"
Una vez cargada, puedes utilizar las clases y los métodos que expone.
6.1. Ejecutar un método de instancia
Creamos un objeto de la clase Utilidades:
$objeto = New-Object MiBiblioteca.Utilidades
Ahora ejecutamos el método Saludar:
$objeto.Saludar("Windows")
Resultado:
Hola, Windows
6.2. Sumar dos números
$objeto.Sumar(10, 20)
Resultado:
30
6.3. Multiplicar dos números
$objeto.Multiplicar(5, 6)
Resultado:
30
6.4. Ejecutar un método estático
Como ObtenerVersion() se ha declarado con la palabra clave static, no necesitamos crear un objeto:
[MiBiblioteca.Utilidades]::ObtenerVersion()
Resultado:
1.0.0
Esta es una de las diferencias fundamentales entre los métodos estáticos y los métodos de instancia.
7. Cargar una DLL con Reflection.Assembly.LoadFile()
Otra posibilidad consiste en utilizar la clase System.Reflection.Assembly.
La reflexión permite consultar los tipos, los métodos, las propiedades y otros elementos que contiene un ensamblado.
Por ejemplo:
$carga = [Reflection.Assembly]::LoadFile(
"C:\MiDLL\MiBiblioteca.dll"
)
La variable $carga contiene una referencia al ensamblado cargado.
Podemos comprobar el nombre del ensamblado:
$carga.FullName
También podemos consultar su ubicación:
$carga.Location
Importante: LoadFile() carga el ensamblado desde una ruta concreta. No garantiza que sus dependencias estén disponibles ni que la biblioteca sea compatible con el entorno de ejecución.
8. Consultar los tipos exportados de una DLL
Una vez cargada la biblioteca, podemos consultar los tipos públicos que exporta.
Utiliza:
$carga | Select-Object ExportedTypes
Para obtener una salida más clara:
$carga.ExportedTypes |
Select-Object FullName
En nuestra biblioteca aparecerá un resultado similar a este:
FullName
--------
MiBiblioteca.Utilidades
También podemos consultar los nombres simples:
$carga.ExportedTypes |
Select-Object Name
Resultado:
Name
----
Utilidades
8.1. Obtener los tipos mediante reflexión
Si queremos recorrer todos los tipos exportados:
foreach ($tipo in $carga.ExportedTypes) {
Write-Host "Tipo: $($tipo.FullName)"
}
Este procedimiento es útil cuando no conocemos de antemano el contenido de una biblioteca.
Hay que tener presente que ExportedTypes devuelve los tipos exportados por el ensamblado, no necesariamente todos los tipos internos o privados.
9. Consultar los métodos de una clase
Podemos obtener el tipo MiBiblioteca.Utilidades directamente desde el ensamblado:
$tipo = $carga.GetType("MiBiblioteca.Utilidades")
$tipo
Para consultar sus métodos:
$tipo.GetMethods() |
Select-Object Name, ReturnType
Esta consulta devuelve los métodos públicos, incluidos los heredados.
Si solo queremos los métodos declarados directamente en la clase, podemos utilizar:
$tipo.GetMethods(
[Reflection.BindingFlags] "Public, NonPublic, Instance, Static, DeclaredOnly"
) |
Select-Object Name, IsStatic, ReturnType
Con este enfoque también podemos inspeccionar métodos no públicos, aunque su visibilidad no implica que debamos o podamos invocarlos libremente.
9.1. Consultar los nombres de los métodos
Si queremos mostrar únicamente los nombres:
$tipo.GetMethods().Name
Para mostrar los métodos declarados directamente en el tipo:
$tipo.DeclaredMethods.Name
La propiedad DeclaredMethods está disponible en los objetos System.Type de .NET Framework.
9.2. Utilizar la sintaxis de tipo de PowerShell
Cuando PowerShell ha resuelto el tipo, también podemos utilizar una expresión como esta:
[MiBiblioteca.Utilidades].DeclaredMethods.Name
O, para la clase del ejemplo ClassLibrary3:
[ClassLibrary3.Class1].DeclaredMethods.Name
Esta última expresión presupone que ClassLibrary3.dll contiene el tipo público ClassLibrary3.Class1 y que PowerShell puede resolverlo.
Si la sintaxis entre corchetes no funciona después de cargar una DLL mediante LoadFile(), utiliza $carga.GetType("ClassLibrary3.Class1"). La resolución de tipos puede variar según el entorno y el contexto de carga del ensamblado.
10. Cargar una DLL desde una ruta de Visual Studio
Supongamos que has creado una biblioteca de clases en Visual Studio y el archivo generado se encuentra en:
C:\Users\juan\source\repos\ClassLibrary3\ClassLibrary3\bin\Debug\ClassLibrary3.dll
Puedes cargarla desde PowerShell de esta manera:
$carga = [Reflection.Assembly]::LoadFile(
"C:\Users\juan\source\repos\ClassLibrary3\ClassLibrary3\bin\Debug\ClassLibrary3.dll"
)
Consulta los tipos exportados:
$carga | Select-Object ExportedTypes
O muestra sus nombres completos:
$carga.ExportedTypes |
Select-Object FullName
Para consultar los métodos de Class1:
$tipo = $carga.GetType("ClassLibrary3.Class1")
if ($null -eq $tipo) {
throw "No se ha encontrado ClassLibrary3.Class1 en la DLL."
}
$tipo.DeclaredMethods.Name
Este último enfoque es especialmente útil porque no depende de que PowerShell resuelva el tipo mediante su nombre entre corchetes.
11. Ejecutar un método estático mediante una DLL
También podemos cargar una biblioteca que contenga una clase llamada Hola.Hola y ejecutar su método estático Main().
Supongamos que la DLL se encuentra en:
C:\Users\pepitogrillo\ejemplo.dll
El código de ejemplo sería:
$carga = [Reflection.Assembly]::LoadFile(
"C:\Users\pepitogrillo\ejemplo.dll"
)
[Hola.Hola]::Main()
Este ejemplo funciona si el ensamblado contiene el tipo Hola.Hola y un método estático público Main() que pueda invocarse sin argumentos.
11.1. Alternativa utilizando el objeto Assembly
Si PowerShell no resuelve directamente el tipo [Hola.Hola], puedes obtenerlo a través del ensamblado:
$carga = [Reflection.Assembly]::LoadFile(
"C:\Users\pepitogrillo\ejemplo.dll"
)
$tipo = $carga.GetType("Hola.Hola")
if ($null -eq $tipo) {
throw "No se ha encontrado el tipo Hola.Hola."
}
$tipo.GetMethods(
[Reflection.BindingFlags] "Public, Static, DeclaredOnly"
) |
Select-Object Name, ReturnType
Si el método Main() existe, es público, estático y no requiere argumentos:
$metodo = $tipo.GetMethod(
"Main",
[Reflection.BindingFlags] "Public, Static"
)
if ($null -eq $metodo) {
throw "No se ha encontrado el método estático Main."
}
$metodo.Invoke($null, [object[]]@())
En Invoke(), el primer argumento representa el objeto sobre el que se invoca el método. Para un método estático se utiliza $null. El segundo argumento contiene los parámetros; en este caso, un array vacío.
12. Crear una DLL que contenga la clase Hola.Hola
Para probar el ejemplo anterior, podemos crear una biblioteca específica.
Primero, generamos el archivo de código:
$codigoHola = @' using System; namespace Hola { public class Hola { public static void Main() { Console.WriteLine("Hola desde una DLL ejecutada con PowerShell"); } public static string ObtenerMensaje() { return "Biblioteca Hola cargada correctamente"; } } } '@ Set-Content-Path "C:\MiDLL\Hola.cs"-Value $codigoHola-Encoding UTF8
Compilamos la biblioteca con el compilador que hemos localizado anteriormente:
& $csc
/nologo
/target:library
/out:"C:\MiDLL\Hola.dll"
"C:\MiDLL\Hola.cs"
if ($LASTEXITCODE -ne 0) {
throw "No se ha podido compilar Hola.dll."
}
Ahora la cargamos:
$cargaHola = [Reflection.Assembly]::LoadFile(
"C:\MiDLL\Hola.dll"
)
Y ejecutamos el método estático:
$tipoHola = $cargaHola.GetType("Hola.Hola")
$metodoMain = $tipoHola.GetMethod(
"Main",
[Reflection.BindingFlags] "Public, Static"
)
$metodoMain.Invoke($null, [object[]]@())
La salida esperada es:
Hola desde una DLL ejecutada con PowerShell
También podemos llamar al método ObtenerMensaje():
$tipoHola.GetMethod("ObtenerMensaje").Invoke(
$null,
[object[]]@()
)
Resultado:
Biblioteca Hola cargada correctamente
En este ejemplo Main() es simplemente un método estático de una clase de biblioteca. No significa que la DLL sea un ejecutable independiente.
13. Invocar métodos de instancia mediante reflexión
No todos los métodos son estáticos. Cuando un método pertenece a una instancia, primero tenemos que crear el objeto correspondiente.
Podemos recuperar el tipo de nuestra biblioteca:
$carga = [Reflection.Assembly]::LoadFile(
"C:\MiDLL\MiBiblioteca.dll"
)
$tipo = $carga.GetType("MiBiblioteca.Utilidades")
Creamos una instancia:
$objeto = [Activator]::CreateInstance($tipo)
Buscamos el método Saludar:
$metodo = $tipo.GetMethod("Saludar")
Lo invocamos con un parámetro:
$metodo.Invoke(
$objeto,
[object[]]@("PowerShell")
)
Resultado:
Hola, PowerShell
La diferencia es que Invoke() recibe el objeto de destino cuando se trata de un método de instancia. Para un método estático, el primer argumento debe ser $null.
14. Consultar los parámetros de los métodos
La reflexión permite conocer los parámetros que recibe cada método antes de ejecutarlo.
Por ejemplo:
$tipo = $carga.GetType("MiBiblioteca.Utilidades")
$tipo.GetMethods() |
Where-Object {
$_.DeclaringType.FullName -eq "MiBiblioteca.Utilidades"
} |
ForEach-Object {
[PSCustomObject]@{
Metodo = $_.Name
Retorno = $_.ReturnType.Name
Estatico = $_.IsStatic
Parametros = (
$_.GetParameters() |
ForEach-Object {
"$($_.ParameterType.Name) $($_.Name)"
}
) -join ", "
}
} |
Format-Table -AutoSize
La salida mostrará información similar a esta:
Metodo Retorno Estatico Parametros
------ ------- -------- ----------
Saludar String False String nombre
Sumar Int32 False Int32 a, Int32 b
Multiplicar Int32 False Int32 a, Int32 b
ObtenerVersion String True
La columna Estatico indica si el método se puede invocar directamente sobre la clase.
15. Compilar una DLL con Add-Type
Además de utilizar csc.exe, Windows PowerShell 5.1 permite compilar código C# con Add-Type cuando el compilador y el entorno de .NET necesarios están disponibles.
Por ejemplo:
$codigo = @'
namespace OtraBiblioteca
{
public class Calculadora
{
public static int Restar(int a, int b)
{
return a - b;
}
}
}
'@
Add-Type
-TypeDefinition $codigo
-Language CSharp
-OutputAssembly "C:\MiDLL\OtraBiblioteca.dll"
-OutputType Library
Si la compilación finaliza correctamente, se generará OtraBiblioteca.dll.
Puedes comprobarlo:
Test-Path "C:\MiDLL\OtraBiblioteca.dll"
Y cargarla:
Add-Type -Path "C:\MiDLL\OtraBiblioteca.dll"
[OtraBiblioteca.Calculadora]::Restar(20, 5)
Resultado:
15
Este procedimiento es cómodo para bibliotecas pequeñas. Para proyectos más complejos, con dependencias, varias clases o diferentes versiones de .NET, resulta más apropiado utilizar un proyecto de biblioteca y compilarlo con el SDK de .NET.
16. Diferencias entre Add-Type y Assembly.LoadFile()
Aunque ambos mecanismos permiten cargar ensamblados, no son exactamente equivalentes.
| Característica | Add-Type -Path | Assembly.LoadFile() |
|---|---|---|
| Cargar una DLL | Sí | Sí |
| Acceder a los tipos | Sí | Sí, mediante el ensamblado cargado |
| Inspeccionar tipos y métodos | Sí | Sí |
| Compilar código fuente | También puede hacerlo mediante -TypeDefinition | No compila código fuente |
| Obtener una referencia al ensamblado | No es su finalidad principal | Sí, devuelve un objeto Assembly |
| Inspección mediante reflexión | Posible | Directa mediante el objeto devuelto |
| Gestión de dependencias | Depende del entorno de carga | Requiere comprobar la resolución de dependencias |
Add-Type es una opción sencilla cuando queremos utilizar clases desde PowerShell. Assembly.LoadFile() resulta especialmente útil cuando queremos conservar una referencia al ensamblado e inspeccionarlo explícitamente.
17. Errores frecuentes al cargar DLL
Error: no se encuentra el archivo
Comprueba la ruta:
Test-Path "C:\MiDLL\MiBiblioteca.dll"
Si devuelve False, revisa el nombre y la ubicación de la biblioteca.
Error: no se encuentra el tipo
Consulta los tipos exportados:
$carga.ExportedTypes |
Select-Object FullName
Es posible que el nombre completo de la clase sea distinto del que estás utilizando.
Error: no se encuentra el método
Inspecciona los métodos declarados:
$tipo.DeclaredMethods |
Select-Object Name, IsStatic
Comprueba el nombre, la visibilidad, si el método es estático y los parámetros que necesita.
Error: no se puede cargar una dependencia
Una DLL puede depender de otros ensamblados. Comprueba que las dependencias necesarias estén disponibles y sean compatibles con el entorno de ejecución.
Error: incompatibilidad entre .NET Framework y .NET moderno
Una biblioteca compilada para .NET Framework no es necesariamente compatible con PowerShell 7, que utiliza .NET moderno.
Comprueba la versión de PowerShell:
$PSVersionTable.PSVersion
Para proyectos destinados a .NET moderno, utiliza el SDK de .NET y el sistema de compilación correspondiente.
Error: la DLL está cargada, pero el código no funciona
Cargar un ensamblado no garantiza que sus métodos se puedan ejecutar correctamente. Puede haber dependencias ausentes, parámetros incorrectos, excepciones internas o incompatibilidades.
Cuando se utiliza reflexión, las excepciones producidas por un método invocado suelen aparecer envueltas en una TargetInvocationException. La propiedad InnerException contiene la excepción original.
18. Consideraciones de seguridad
Las bibliotecas DLL pueden ejecutar código dentro del proceso que las carga. Por ello:
- No cargues DLL de origen desconocido o no confiable.
- Comprueba la procedencia y la integridad de los archivos.
- Revisa las dependencias antes de ejecutar métodos.
- Evita ejecutar PowerShell con privilegios de administrador cuando no sea necesario.
- No confundas la inspección de una DLL con una garantía de que su contenido sea seguro.
- Ten en cuenta que cargar código de terceros puede modificar el estado del proceso o acceder a recursos a los que este tenga permiso.
La reflexión no es un mecanismo de aislamiento de seguridad. Si necesitas ejecutar código no confiable, utiliza un entorno aislado adecuado.
19. Resumen de comandos
Estos son los comandos principales del tutorial:
| Operación | Comando |
|---|---|
| Comprobar PowerShell | $PSVersionTable.PSVersion |
| Compilar una DLL | csc.exe /target:library |
| Cargar una biblioteca | Add-Type -Path "C:\MiDLL\MiBiblioteca.dll" |
| Cargar mediante reflexión | [Reflection.Assembly]::LoadFile("C:\MiDLL\MiBiblioteca.dll") |
| Consultar tipos exportados | $carga.ExportedTypes |
| Obtener un tipo | $carga.GetType("MiBiblioteca.Utilidades") |
| Consultar métodos | $tipo.GetMethods() |
| Crear una instancia | [Activator]::CreateInstance($tipo) |
| Invocar un método estático | $tipo.GetMethod("Main").Invoke($null, [object[]]@()) |