Powershell to display Regsvr32 result in console instead of dialog - powershell

I've searched but did not find any answer.
The task is register one dll using Powershell ps1, followed by other lines of scripts. I don't want to be interrupted by the dialog, so added the /s parameter. But now the result information is ignored, no matter succeed or fail.
I want the result displayed in console. But how?

Launch regsvr32.exe /s with Start-Process -PassThru and inspect the ExitCode property:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s C:\path\to\your.dll" -PassThru
$regsvrp.WaitForExit(5000) # Wait (up to) 5 seconds
if($regsvrp.ExitCode -ne 0)
{
Write-Warning "regsvr32 exited with error $($regsvrp.ExitCode)"
}

Here is a more complete full powershell cmdlet with pipeline support.
function Register-Dll
{
<#
.SYNOPSIS
A function that uses the utility regsvr32.exe utility to register a file
.PARAMETER Path
The file path
.PARAMETER Unregister
when specified, unregisters instead of registers
#>
[CmdletBinding()]
param (
[ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' })]
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipeLineByPropertyName=$true)]
[Alias("FullName")]
[string[]]$Path,
[Alias("u")]
[switch]$Unregister
)
begin {
if ($Unregister)
{
$regflag = "-u "
}
else
{
$regflag = ""
}
[int]$NumFailed=0
$RegExitCodes = #{
0="SUCCESS";
1="FAIL_ARGS - Invalid Argument";
2="FAIL_OLE - OleInitialize Failed";
3="FAIL_LOAD - LoadLibrary Failed";
4="FAIL_ENTRY - GetProcAddress failed";
5="FAIL_REG - DllRegisterServer or DllUnregisterServer failed.";
}
}
process {
foreach ($p in $path)
{
try
{
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag <code>$p</code>" -Wait -NoNewWindow -PassThru
if($regsvrp.ExitCode -ne 0)
{
$NumFailed++
Write-Error "regsvr32 $regflag for $p exited with error $($regsvrp.ExitCode) - $($RegExitCodes[$regsvrp.ExitCode])"
}
} catch {
$NumFailed++
Write-Error $_.Exception.Message
}
}
}
end {
if ($NumFailed -gt 0)
{
if ($Unregister)
{
$mode = "unregister"
}
else
{
$mode = "register"
}
Write-Error "Failed to $mode $NumFailed dll's, see previous errors for detail"
}
}
}
Usage:
function Register-MyAppDll
{
param(
[Parameter(Mandatory=$true,ParameterSetName="Both")]
[switch]$ReRegister,
[Parameter(Mandatory=$true,ParameterSetName="UnregisterOnly")]
[Alias("u")]
[switch]$UnRegister,
[Parameter(Mandatory=$true,ParameterSetName="RegisterOnly")]
[Alias("r")]
[switch]$Register
)
$RegOptions = #()
if ($UnRegister -or $ReRegister) { $RegOptions += #{Unregister=$true} }
if ($Register -or $ReRegister) { $RegOptions += #{} }
$dlltoregister = Get-ChildItem "C:\MyApp\bin" -Filter *.dll | where {$_ -notmatch '^interop'}
foreach ($RegOpt in $RegOptions)
{
$dlltoregister | Register-Dll #RegOpt
}
}
Register-MyAppDll -UnRegister
Register-MyAppDll -Register
Register-MyAppDll -ReRegister
Enjoy :)

Thank you Justin! I'm using this script and it works great.
There seems to be a typo in the following line of code:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag <code>$p</code>" -Wait -NoNewWindow -PassThru
The code tag shoudn't be there. I changed it to the following with added escaped double quotes around path to support spaces in paths:
$regsvrp = Start-Process regsvr32.exe -ArgumentList "/s $regflag `"$p`"" -Wait -NoNewWindow -PassThru

Related

Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process

I have the following PowerShell script:
param([switch]$Elevated)
function Test-Admin
{
$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if ((Test-Admin) -eq $false) {
if ($elevated) {
# tried to elevate, did not work, aborting
} else {
Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated ' -f ($myinvocation.MyCommand.Definition))
}
exit
}
function UpdateHosts {
param ($hostName)
Write-Host $hostName
try {
$strHosts = (Get-Content C:\WINDOWS\system32\drivers\etc\hosts -Raw)
if([string]::IsNullOrEmpty($strHosts)) {
Write-Error "Get-Content hosts empty"
exit
}
} catch {
Write-Error "Unable to read hosts file"
Write-Error $_
exit
}
try {
$strHosts -replace "[\d]+\.[\d]+\.[\d]+\.[\d]+ $hostName","$ipAddress $hostName" | Set-Content -Path C:\WINDOWS\system32\drivers\etc\hosts
} catch {
Write-Error "Unable to write hosts file"
Write-Error $_
exit
}
}
$ipAddress = "127.0.0.1"
UpdateHosts -hostName local.pap360.com
Sometimes, when I run it, I get the following error:
Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process.
When I open up C:\WINDOWS\system32\drivers\etc\hosts in Notepad it's then blank. ie. all the data I had in it is wiped.
My question is... how can I prevent this from happening?
Like if Set-Content can't access the hosts file to write to it then how is it able to wipe it's contents? And why isn't the catch block working?
Here's the full error:
Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by
another process.
At C:\path\to\test.ps1:36 char:92
+ ... $hostName" | Set-Content -Path C:\WINDOWS\system32\drivers\etc\hosts
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\WINDOWS\system32\drivers\etc\hosts:String) [Set-Content], IOException
+ FullyQualifiedErrorId : GetContentWriterIOError,Microsoft.PowerShell.Commands.SetContentCommand
I also don't understand why it's so intermittent. Is there some Windows process that opens the hosts file up for 1s once a minute or some such?
First of all, check if your Firewall or AV software isn't restricting access to the file.
If that is not the case and 'some' other process is currently locking the hosts file, perhaps add a test before reading or writing the file can help:
function Test-LockedFile {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName', 'FilePath')]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$Path
)
$file = [System.IO.FileInfo]::new($Path)
# old PowerShell versions use:
# $file = New-Object System.IO.FileInfo $Path
try {
$stream = $file.Open([System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
return $false # file is not locked
}
catch {
return $true # file is locked
}
}
Then use like this:
function UpdateHosts {
param ($hostName)
Write-Host $hostName
$path = 'C:\WINDOWS\system32\drivers\etc\hosts'
# test if the file is readable/writable
# you can of course also put this in a loop to keep trying for X times
# until Test-LockedFile -Path $path returns $false.
if (Test-LockedFile -Path $path) {
Write-Error "The hosts file is currently locked"
}
else {
try {
$strHosts = (Get-Content $path -Raw -ErrorAction Stop)
if([string]::IsNullOrEmpty($strHosts)) {
Write-Error "Get-Content hosts empty"
exit
}
}
catch {
Write-Error "Unable to read hosts file:`r`n$($_.Exception.Message)"
exit
}
try {
$strHosts -replace "[\d]+\.[\d]+\.[\d]+\.[\d]+\s+$hostName", "$ipAddress $hostName" |
Set-Content -Path $path -Force -ErrorAction Stop
}
catch {
Write-Error "Unable to write hosts file:`r`n$($_.Exception.Message)"
exit
}
}
}

Start-Command PowerShell v3.0, does nothing

I am trying to install software (executable) on several servers with various versions of PowerShell.
Normally below code works with no issue on PS4 and up. On PS3 it does not install anything, nor does it produce any errors on the remove server eventviewer. It treated as success by printing out "... -- installation succeeded" and exits. I've googled about and read that perhaps Start-Process is the culprit in PS3.
Begin {
$uncpath="\\remoteserveruncpath\" #"
$exe_parameter1 = "centralserver.com"
$creds = Get-Credential -Message "Password: " -Username "$($env:userdnsdomain)\$($env:username)"
}
Process {
$dnshostname = "server1","server2","server3"
ForEach ($server in $dnshostname) {
Invoke-Command -ComputerName $server -ScriptBlock {
param($server_int,$exe_parameter1_int,$uncpath_int,$creds_int)
(New-Object -ComObject WScript.Network).MapNetworkDrive('Z:',"$($uncpath_int)", $false, "$($creds_int.Username)", "$($creds_int.GetNetworkCredential().Password)")
$arguments = "/param_1=$exe_parameter1_int /param_2=$($server_int.ToLower()) /start-program=1 /S"
If((Start-Process "Z:\installer.exe" -ArgumentList $arguments -Wait -Verb RunAs).ExitCode -ne 0) {
Write-Host "$server_int -- installation succeeded"
} else {
Write-Error "$server_int -- installation failed"
}
} -ArgumentList $server,$exe_parameter1,$uncpath,$creds;
}
}
Any advice? Many thanks!
Without -PassThru, Start-Process produces no output, so accessing .ExitCode effectively returns $null, always.
And since $null -ne 0 is always $true, your code always indicates success.
In order to get the installer command's true exit code, you therefore need to use the following (note the addition of -PassThru):
if ((Start-Process -PassThru 'Z:\installer.exe' -ArgumentList $arguments -Wait -Verb RunAs).ExitCode -ne 0) { ... }

Returning value from Start-Process argument

I have a PowerShell function (Add-EventLogSource) that checks if an event log source exists. If it does not exist and the shell is not elevated, I start a new, elevated shell and call the function again.
I can't seem to get the return values correct. If the event log source does not exist, and I call Add-EventLogSource, I am not getting the return value all the way back to instance that originally called Add-EventLogSource. Can anyone see the problem? The code looks like this:
Function Add-EventLogSource {
Param (
[Parameter(Mandatory=$True)]
$EventLogSource
)
# Check if $EventLogSource exists as a source. If the shell is not elevated and the check fails to access the Security log, assume the source does not exist.
Try {
$sourceExists = [System.Diagnostics.EventLog]::SourceExists("$EventLogSource")
}
Catch {
$sourceExists = $False
}
If ((([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] “Administrator”)) -AND ($sourceExists -eq $False)) { # Shell is elevated...
Try {
New-EventLog –LogName Application –Source $EventLogSource -ErrorAction Stop
}
Catch {
Return "Error"
}
Return "Created"
}
ElseIf ($sourceExists -eq $False) {
$return = Start-Process PowerShell –Verb RunAs -ArgumentList "Add-EventLogSource -EventLogSource $EventLogSource; start-sleep 5" -Wait
Return $return
}
Else {
Return "Exists"
}
}
Thanks.

How to loop through a script to let a user selection different options?

Fairly new to Powershell. I'm working on a script that will let a user install different pieces of software that will be needed. This process is currently being done manually and it can take 30-45 minutes to install everything that is needed. However, not everything needs to be installed on each workstation so I need flexibility for the user. This is what I have come up with so far. (Modified for brevity)
$Software1Path = "Path to installer"
$Software2Path = "Path to installer"
$Software3Path = "Path to installer"
function Software1 {
$Software1Arguments = "/S"
Start-Process -FilePath $Software1Installer $Software1Arguments -Wait
InstallOptions
}
function Software2 {
$Software2Arguments = "/silent"
Start-Process -FilePath $Software2Installer $Software2Arguments -Wait
InstallOptions
}
function Software3 {
$Software3Arguments = "/passive"
Start-Process -FilePath $Software3Installer $Software3Arguments -Wait
InstallOptions
}
function InstallOptions {
Do {
Clear-Host
Write-Host('1. Install Software1')
Write-Host('2. Install Software1')
Write-Host('3. Install Software1')
Write-Host('4. Install All Three')
Write-Host('0. Exit')
$value = Read-Host 'Input your selection (0-3)'
}
Until ($value -eq "o"){
switch ($value) {
"0" {exit}
"1" { Software 1}
"2" { Software 2}
"3" { Software 3}
"4" { Software1
Software2
Software3}
}
}
}
It does not give the desired result. I can either install one piece of software but then the script exits and I also cannot install all three. I've played with the InstallOptions and wrote it ten different ways but I am still not getting the desired result. Any suggestions?
You can call InstallOptions in a while($true) {} (as long as you keep 0 to exit) or another type of loop to make it return to the menu. If you call the menu from the software installation-function it will never get to 2 and 3 when you use "install all". Try:
$Software1Path = "Path to installer"
$Software2Path = "Path to installer"
$Software3Path = "Path to installer"
function Software1 {
$Software1Arguments = "/S"
Start-Process -FilePath $Software1Path -ArgumentList $Software1Arguments -Wait
}
function Software2 {
$Software2Arguments = "/silent"
Start-Process -FilePath $Software2Path -ArgumentList $Software2Arguments -Wait
}
function Software3 {
$Software3Arguments = "/passive"
Start-Process -FilePath $Software3Path -ArgumentList $Software3Arguments -Wait
}
function InstallOptions {
Clear-Host
Write-Host('1. Install Software1')
Write-Host('2. Install Software2')
Write-Host('3. Install Software3')
Write-Host('4. Install All Three')
Write-Host('0. Exit')
$value = Read-Host 'Input your selection (0-3)'
switch ($value) {
"0" {exit}
"1" { Software1}
"2" { Software2}
"3" { Software3}
"4" {
Software1
Software2
Software3
}
}
}
#Start the train
while($true) { InstallOptions }
You might also want to clean this up. Ex. $Sofware1Path is outside the function while the $Software1Arguments are inside. For simple installations, you could clean this up to use ex. a csv-stored array (can be read stored in a separate file if needed). Something like:
$Installations = #(#"
Name,Step,Path,Arguments
Software1,1,"c:\Install Files\Product1\Setup.exe",/S
Software2,2,"c:\Install Files\Product2\SetupPart2.exe",/silent
Software2,1,"c:\Install Files\Product2\Setup.exe","/silent ""space test with ,"""
Software3,1,"c:\Install Files\Product3\Setup.exe",/passive "space test"
"# | ConvertFrom-Csv)
function InstallSoftware ($Name) {
Write-Host "Installing $Name..."
$Installations | Where-Object { $_.Name -eq $Name } | Sort-Object { $_.Step -as [int] } | ForEach-Object {
ExecuteStep -Path $_.Path -Arguments $_.Arguments
}
}
function ExecuteStep ($Path, $Arguments) {
Write-Host "Executing '$Path' '$Arguments'"
Start-Process -FilePath $Path -ArgumentList $Arguments -Wait
}
function Menu {
$UniqueSoftware = $Installations | Select-Object -ExpandProperty Name -Unique | Sort-Object
$NumOfSoftware = $UniqueSoftware.Count
#Generate menu
Clear-Host
for($i=0;$i -lt $NumOfSoftware; $i++) {
Write-Host ("{0}. Install {1}" -f ($i+1), $Software[$i].Name)
}
Write-Host ("{0}. Install all" -f ($NumOfSoftware+1))
Write-Host "0. Exit"
do {
#Get input
$value = (Read-Host "Input your selection (0-$($NumOfSoftware+1))") -as [int]
#Execute
switch ($value) {
0 { exit }
{ $_ -gt 0 -and $_ -le $NumOfSoftware } { InstallSoftware -Name $UniqueSoftware[($_-1)] }
($NumOfSoftware+1) { 0..($NumOfSoftware-1) | ForEach-Object { InstallSoftware -Name $UniqueSoftware[($_)] } }
default { Write-Host "Invalid input..." }
}
#Validate input or retry
} until ( $value -ge 0 -and $value -le $NumOfSoftware+1 )
}
#Start the train
while($true) { Menu }

Run parameters in a Powershell script from a batch file

I'm trying to specify parameters in a powershell script, from a batch file.
The script itself looks likes this:
Param(
[Parameter(Mandatory=$true,HelpMessage="Application switch")]
[string[]]$Apps,
[ValidateRange(3,9999)]
[int]$SwitchDelay = 30
$AppPids = #()
$Windows = Get-Process | ? { $_.MainWindowTitle -ne "" }
$Wsh = New-Object -COM Wscript.Shell
foreach ($App in $Apps) {
foreach ($Window in $Windows) {
if ($Window.MainWindowTitle -like $App) {
Write-Verbose "Vindusfilter ""$App"" found hit on ""$($Window.MainWindowTitle)"" med PID ""$($Window.Id)"""
$AppPids += $Window.Id
}
}
}
do {
foreach ($ID in $AppPIDS) {
# Hides text...
$Wsh.AppActivate($ID) | Out-Null
Start-Sleep -Seconds $SwitchDelay
Write-Verbose "Changed window to PID ""$ID"""
}
} while ($true)
And what I'm trying to do is to define in a batch file is something like:
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File "C:\AppRotate.ps1" -Apps "*Chrome*", "Spotify*" -Switchdelay 5
pause
(Supposed to show error message here, need more reputation first...)
Error: "... PositionalParameterNotFound.Approtate.ps1"
I'm basically new to scripting, so any ideas?
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File "C:\AppRotate.ps1" -Apps "*Chrome*","Spotify*" -Switchdelay 5
The problem was the space between the first, and second variable of parameter -Apps.
Should work now.