I need to continuously watch a particular "drop" folder for (tiny) pdfs and auto-print and delete them. The printing itself will be handled by SumatraPDF (because I don't think there is a built-in, Windows method).
Creating a temporary WMI event monitor in PowerShell is pretty straightforward:
$WQLquery = "SELECT * FROM __InstanceCreationEvent WITHIN 5 " +
"WHERE TargetInstance ISA 'CIM_DataFile' " +
"AND TargetInstance.Extension = 'pdf' " +
"AND TargetInstance.Drive = '$($Drive):' " +
"AND TargetInstance.Path = '$($Folder.replace('\','\\'))'"
$Action = {
$TimedOut = $null
$PrintProcess = Start-Process -FilePath $Sumatra -ArgumentList "-Print-To $Printer `"$($EventArgs.NewEvent.TargetInstance.Name)`"" -PassThru
$PrintProcess | Wait-Process -Timeout 10 -ErrorAction SilentlyContinue -ErrorVariable TimedOut
if ($TimedOut) {
"Printing $($EventArgs.NewEvent.TargetInstance.Name) timed out." | Out-File $ErrorLogPath -Append
$PrintProcess.kill
} elseif ($PrintProcess.ExitCode -ne 0) {
"Error for $($EventArgs.NewEvent.TargetInstance.Name) : $($PrintProcess.ExitCode)" | Out-File $ErrorLogPath -Append
} else {
Remove-Item $EventArgs.NewEvent.TargetInstance.Name -Force
}
}
Register-CimIndicationEvent -Query $wqlquery -SourceIdentifier 'FileCreated' -Action $Action
Because it uses a scriptblock, the variables referencing the event aren't processed until $Action is called after each event.
For permanent event monitors with string properties rather than scriptblocks, how do I use the event properties in the consumer?
I'm hoping to call a PowerShell script using CommandLineEventConsumer and pass it the file name that triggered the event. For example:
$FilterArgs = #{
ClassName = '__EventFilter'
NameSpace = 'root\subscription'
ErrorAction = 'Stop'
Property = #{
Name = 'PDFCreatedFilter'
EventNamespace = 'root\CIMV2'
QueryLanguage = 'WQL'
Query = $WQLQuery
}
}
$FilterInstance = New-CimInstance #FilterArgs
$ConsumerArgs = #{
ClassName = 'CommandLineEventConsumer'
NameSpace = 'root\subscription'
ErrorAction = 'Stop'
KillTimeout = 10
Property = #{
Name = 'FileCreatedConsumer'
EventNamespace = 'root\CIMV2'
ExecutablePath = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
CommandLineTemplate = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe ' +
'-ExecutionPolicy bypass -NoProfile -file "$MyScriptPath" ' +
"-Name `"$($EventArgs.NewEvent.TargetInstance.Name)`" -Printer $MyPrinterName"
}
}
$ConsumerInstance = New-CimInstance #ConsumerArgs
However, that $EventArgs.NewEvent.TargetInstance.Name will not be defined at the time I create the consumer.
If an ActiveScriptEventConsumer using the ScriptText parameter is the only way to do this, I can probably stumble through a VBscript, but I still would need the syntax to get it to work.
Thanks.
Script originally created by GhillieHammer
I've added additional features to work for me.
# $drives = #("D","E","F");
$drives = $null
# The minimum disk size to check for raising the warning
$minSize = 20GB
$MathminSize = [math]::round($minSize/1GB,2)
$minString = ($MathminSize).ToString()
$minString = $minString + "GB"
$email_to_addressArray = #("");
# $email_to_addressArray = #("toSingle#company.com")
#Set a couple needed variables
$SendIt = $Null
$ThisHost = $env:computername
$IPAddy = Invoke-RestMethod -Uri ('http://ipinfo.io/'+(Invoke-WebRequest -uri "http://ifconfig.me/ip").Content) | Select ip
#Check for $Drives query mode and make an array of them
if ($drives -eq $null -Or $drives -lt 1) {
$localVolumes = Get-WmiObject win32_volume;
$drives = #();
foreach ($vol in $localVolumes) {
if ($vol.DriveType -eq 3 -And $vol.DriveLetter -ne $null ) {
$drives += $vol.DriveLetter[0];
}
}
}
# Enumerate through the array of drives
# check to see if any are below minimum
# if so, set a flag saying as much and then
# add them and their information to the array we'll be adding to the email
foreach ($d in $drives) {
$disk = Get-PSDrive $d;
$MathDiskFree = [math]::round($disk.Free/1GB,2)
$MathDiskUsed = [math]::round($disk.Used/1GB,2)
$MathDisktotal = [math]::round($MathDiskFree + $MathDiskUsed)
$MathDiskPerc = ($MathDiskFree / $MathDiskUsed).tostring("P")
if ($disk.Free -lt $minSize) {
$SendIt = 1
$space += ("Free space on drive " + $d + " = " + $MathDiskFree + "GB. This is equal to only " + $MathDiskPerc + " of the " + $MathDisktotal + "GB total space available on this drive.<br>")
}
}
# Check the flag to see if it's set, meaning there's at least one drive below minimum free space, and if so, fire off the email(s)
If ($SendIt -eq 1) {
# Enumerate through the array of email addresses and fire off a formatted email to each
foreach ($toAddress in $email_to_addressArray) {
$User = "diskspace#"
$File = (Get-Content C:\Temp\pw.txt | ConvertTo-SecureString)
$MyCredential = New-Object -TypeName System.Management.Automation.PSCredential `
-ArgumentList $User, $File
$To = $toAddress
$from = ""
$EmailSubject = "WARNING: one or more disk on $ThisHost low on space"
$smtp = "auth.smtp.1and1.co.uk"
$DefaultMessage="
<p>Dear colleague,</p>
<p>There is a hard drive space issue on $ThisHost IPv4: $IPAddy </p>
<p>$space<br></p>
<p>This message only fires off if one or more disks have less than $minString GB of free space left.</p>
<p>Sincerely,<br>
Robot Monitor.<br><br>
</p>"
$MailMessage = #{
To = $To
From = $from
# BCC = $Bcc
Subject = $EmailSubject
Body = $DefaultMessage
priority = "High"
Smtpserver = $smtp
Credential = $MyCredential
ErrorAction = "SilentlyContinue"
}
Send-MailMessage #MailMessage -bodyashtml
}
}
The script works perfectly but can't seem to get it stop generating the following information
Can't seem to find or understand how it's generating additional lines of text. Any insight would be greatly appreciated!
Try using a more simplified code like the one below. I use this with a scheduled task on the machines I need it on. It only sends on email if disk space gets below threshold. You could modify it to work with the other drives you have. It's not really clear how you are getting multiple line output but modifying it would help resolve your problem and simplify your code.
$minGbThreshold = 10;
$computers = $env:COMPUTERNAME;
$smtpAddress = "smtp.yourdomain.com";
$toAddress = "anyone#gmail.com";
$fromAddress = "someone#gmail.com";
foreach($computer in $computers)
{
$disks = Get-WmiObject -ComputerName $computer -Class Win32_LogicalDisk -Filter "DriveType = 3";
$computer = $computer.toupper();
$deviceID = $disk.DeviceID;
foreach($disk in $disks)
{
$freeSpaceGB = [Math]::Round([float]$disk.FreeSpace / 1073741824, 2);
if($freeSpaceGB -lt $minGbThreshold)
{
$smtp = New-Object Net.Mail.SmtpClient($smtpAddress)
$msg = New-Object Net.Mail.MailMessage
$msg.To.Add($toAddress)
$msg.From = $fromAddress
$msg.Subject = “Diskspace below threshold ” + $computer + "\" + $disk.DeviceId
$msg.Body = $computer + "\" + $disk.DeviceId + " " + $freeSpaceGB + "GB Remaining";
$smtp.Send($msg)
}
}
}
I am using the excellent Chrome-Kiosk script from https://github.com/alex-tomin/Tomin.Tools.KioskMode and it works great. I have modified it for our display boards at work and we use it flawlessly with 11 screens.
I wanted to modify it to make a single screen launcher, so I can open a small GUI box, enter the URL to display, and then the screen number that it should display on. I have created a small script and it works perfectly on my local machine. What I want to do is open the GUI on my screen and then send the two variables over the network to the display PC on the network. I had hoped that I would be able to do this with remote execution as found here: https://www.howtogeek.com/117192/how-to-run-powershell-commands-on-remote-computers/, but no luck.
Here is the GUI code:
function button ($title,$mailbx, $WF, $TF) {
[void][System.Reflection.Assembly]::LoadWithPartialName( "System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName( "Microsoft.VisualBasic")
$form = New-Object "System.Windows.Forms.Form";
$form.Width = 500;
$form.Height = 150;
$form.Text = $title;
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
$textLabel1 = New-Object "System.Windows.Forms.Label";
$textLabel1.Left = 25;
$textLabel1.Top = 15;
$textLabel1.Text = $mailbx;
$textLabel2 = New-Object "System.Windows.Forms.Label";
$textLabel2.Left = 25;
$textLabel2.Top = 50;
$textLabel2.Text = $WF;
$textBox1 = New-Object "System.Windows.Forms.TextBox";
$textBox1.Left = 150;
$textBox1.Top = 10;
$textBox1.width = 200;
$textBox2 = New-Object "System.Windows.Forms.TextBox";
$textBox2.Left = 150;
$textBox2.Top = 50;
$textBox2.width = 200;
$defaultValue = ""
$textBox1.Text = $defaultValue;
$textBox2.Text = $defaultValue;
$button = New-Object "System.Windows.Forms.Button";
$button.Left = 360;
$button.Top = 85;
$button.Width = 100;
$button.Text = "Ok";
$eventHandler = [System.EventHandler]{
$textBox1.Text;
$textBox2.Text;
$form.Close();
};
$button.Add_Click($eventHandler) ;
# Add controls to all the above objects defined
$form.Controls.Add($button);
$form.Controls.Add($textLabel1);
$form.Controls.Add($textLabel2);
$form.Controls.Add($textLabel3);
$form.Controls.Add($textBox1);
$form.Controls.Add($textBox2);
$form.Controls.Add($textBox3);
$ret = $form.ShowDialog();
return $textBox1.Text, $textBox2.Text#, $textBox3.Text
}
$return= button "Monitoring Screen Selector" "Enter URL" "Enter Screen # from 1 to 11" #"Target Folder"
$return[0]
$return[1]
The first part of the script is the GUI, it passes $return[0] and $return[1] into the second part of the script which is below:
$chromePath = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
$chromeArguments = '--new-window'
# if Window not moved (especially on machine start) - try increasing the delay.
$ChromeStartDelay = 3
Set-Location $PSScriptRoot
. .\HelperFunctions.ps1
# Kill all running instances
# &taskkill /im chrome* /F
Chrome-Kiosk $return[0] -MonitorNum $return[1]
So the GUI should open on the local PC, then send $return[0] and $return[1] to the computer with all of the displays plugged into it so that the second part of the script can receive those two inputs from the GUI and then activate the screen and URL.
The idea as that during an incident or event that isn't covered by our normal screens, we can throw a web page up there until it is resolved, then close it manually afterwards (unless somebody knows how to catch the PID of a specific Chrome instance, which I very much doubt, so that it can be terminated somehow)
Any hints on how I could do this?
Ok, so here is what I have done to make this work. I have created server end scripts that check a folder for 2 specific files, and once it sees them it sends that data to the main script that is expecting a monitor number and a URL. Hope somebody finds this useful.
You will need to go and download the original Chrome-Kiosk from https://alextomin.wordpress.com/2015/04/10/kiosk-mode-in-windows-chrome-on-multiple-displays/
SERVER SCRIPTS
I have put these into a folder called c:\scripts\ICVT
looper.PS1 - This will run constantly
Set-Location -Path C:\scripts\ICVT
while ($true) {
.\file_checker.ps1;
}
file_checker.PS1 - This is the script that looper runs. file_checker scans the folder for web.txt and mon.txt . Both must be present for the rest of the script to execute.
#Checks folder for web.txt and mon.txt . Both must be present for the rest of the
script to execute
Set-Location -Path C:\scripts\ICVT
$a = Test-Path web.txt
$b = Test-Path mon.txt
IF (($a -and $b -eq $True)) {
.\launcher.ps1
}
else {
Write-Host "Scanning For Files"
}
Start-Sleep -Seconds 5
launcher.PS1 - This is just a modified version of the original script
$chromePath = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
$chromeArguments = '--new-window'
$web = (Get-Content -Path web.txt)
$mon = (Get-Content -Path mon.txt)
# if Window not moved (especially on machine start) - try increasing the delay.
$ChromeStartDelay = 5
Set-Location $PSScriptRoot
. .\HelperFunctions.ps1
Chrome-Kiosk $web -MonitorNum $mon
#Delete parameters after use
Start-Sleep -Seconds 5
del web.txt
del mon.txt
CLIENT SIDE
menu.PS1 - If you are wanting to push a specific screen to the display computer over the network then setup Network Path, but if you are connected to the local PC then Local Path is the one to setup. As long as looper is able to see the folder then it will work. There is a little logic in the menu so that the script wont execute if you close the window without putting in any details. (if you run launcher with no parameters it still executes chrome and sends it to a screen that is out of the array normally number 2 for some reason)
#################Local Path###################
Set-Location -Path c:\scripts\ICVT
#################Network Path#################
#Set-Location -Path \\somecomputer\c$\scripts\ICVT
function button ($title,$mailbx, $WF, $TF) {
###################Load Assembly for creating form & button######
[void][System.Reflection.Assembly]::LoadWithPartialName( “System.Windows.Forms”)
[void][System.Reflection.Assembly]::LoadWithPartialName( “Microsoft.VisualBasic”)
#####Define the form size & placement
$form = New-Object “System.Windows.Forms.Form”;
$form.Width = 500;
$form.Height = 150;
$form.Text = $title;
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
##############Define text label1
$textLabel1 = New-Object “System.Windows.Forms.Label”;
$textLabel1.Left = 25;
$textLabel1.Top = 15;
$textLabel1.Text = $mailbx;
##############Define text label2
$textLabel2 = New-Object “System.Windows.Forms.Label”;
$textLabel2.Left = 25;
$textLabel2.Top = 50;
$textLabel2.Text = $WF;
##############Define text label3
#$textLabel3 = New-Object “System.Windows.Forms.Label”;
#$textLabel3.Left = 25;
#$textLabel3.Top = 85;
#$textLabel3.Text = $TF;
############Define text box1 for input
$textBox1 = New-Object “System.Windows.Forms.TextBox”;
$textBox1.Left = 150;
$textBox1.Top = 10;
$textBox1.width = 200;
############Define text box2 for input
$textBox2 = New-Object “System.Windows.Forms.TextBox”;
$textBox2.Left = 150;
$textBox2.Top = 50;
$textBox2.width = 200;
############Define text box3 for input
#$textBox3 = New-Object “System.Windows.Forms.TextBox”;
#$textBox3.Left = 150;
#$textBox3.Top = 90;
#$textBox3.width = 200;
#############Define default values for the input boxes
$defaultValue = “”
$textBox1.Text = $defaultValue;
$textBox2.Text = $defaultValue;
#$textBox3.Text = $defaultValue;
#############define OK button
$button = New-Object “System.Windows.Forms.Button”;
$button.Left = 360;
$button.Top = 85;
$button.Width = 100;
$button.Text = “Ok”;
############# This is when you have to close the form after getting values
$eventHandler = [System.EventHandler]{
$textBox1.Text;
$textBox2.Text;
#$textBox3.Text;
$form.Close();};
$button.Add_Click($eventHandler) ;
#############Add controls to all the above objects defined
$form.Controls.Add($button);
$form.Controls.Add($textLabel1);
$form.Controls.Add($textLabel2);
$form.Controls.Add($textLabel3);
$form.Controls.Add($textBox1);
$form.Controls.Add($textBox2);
$form.Controls.Add($textBox3);
$ret = $form.ShowDialog();
#################return values
return $textBox1.Text, $textBox2.Text#, $textBox3.Text
}
$return= button “Monitoring Screen Selector” “Enter URL” “Enter Screen # from 1 to 11” #“Target Folder”
if ($return[0] -ne "") {
$return[0] > web.txt
}
if ($return[0] -eq "") {
exit
}
if ($return[1] -ne "") {
$return[1] > mon.txt
}
if ($return[0] -eq "") {
exit
}
Ideally, I'd like to write the current status of any apps/svcs that will be changed, so the system could be restored programmatically as well. This is why I chose a CSV file to hold that data.
Here is how far I am in documenting which of each needs to be disabled (and later re-enabled at next boot):
(PowerShell 5 for now)
$svcsRunning = #()
$svcResults = #()
$svcsRunning = Get-WmiObject Win32_Service -Filter "State = 'Running'"
ForEach ($s in $svcsRunning) {
$svcDetails = #{
Date = get-date
ExitCode = $s.ExitCode
Name = $s.Name
ProcessId = $s.ProcessId
StartMode = $s.StartMode
State = $s.State
Status = $s.Status
}
# for iterations
$svcResults += New-Object PSObject -Property $svcDetails
}
# when done, export
$svcResults | export-csv -Path c:\users\yumi\startupServices.csv -NoTypeInformation
$appsRunning = #()
$appResults = #()
$appsRunning = Get-WmiObject Win32_StartupCommand
ForEach ($a in $appsRunning) {
$appDetails = #{
Date = get-date
Caption = $a.Caption
ClassPath = $a.ClassPath
Command = $a.Command
Container = $a.Container
Description = $a.Description
Location = $a.Location
Name = $a.Name
Path = $a.Path
Site = $a.Site
User = $a.User
UserSID = $a.UserSID
}
# for iterations
$appResults += New-Object PSObject -Property $appDetails
}
# when done, export
$appResults | export-csv -Path c:\users\yumi\startupApplications.csv -NoTypeInformation
I'm not sure if the above can show all apps/svcs that need to be disabled for a clean boot.
In an effort to satisfy "The Joel Test" question #2 "Can you make a build in one step?", I'm trying to complete a release candidate build script with the creation of a CD iso from the collection of files gathered and generated by the installer creator.
There seem to be many good tools (many free) out there that will create ISOs, but I need to find one that can be run at the windows command line so I can integrate it into the NAnt build script that's fired off by Cruise Control.
Build environment is:
Windows Server 2003
.NET 1.1 - 3.5 (application we're creating is built on 2.0)
NullSoft installer (NSIS)
CruiseControl.net
NAnt
I've been googling around, but no luck yet.
Anyone have a recommendation?
Try mkisofs. It's part of the cdrecord project.
Creating a simple CD ISO
I've found a significantly easier approach, and it doesn't require Cygwin: CDBurnerXP
It's not really advertised on the site, but it includes a command-line edition, as cdbxpcmd.exe. There is also some documentation about the command-line options.
Of particular interest are the -iso and -format options; used something like:
cdbxpcmd --burn-data -folder:input -iso:output.iso -format:iso -changefiledates
to generate an ISO called output.iso from the files in the input folder
Creating a Bootable ISO
The command line tool doesn't appear to let you make a bootable CD directly. However, if you know your list of files isn't going to change (ie only the content of those files), you could try the following (untested):
Load up the CDBurnerXP GUI version
Add the files interactively
Select Disc->Burn Options...
Set up your boot image
Select File->Save to create a DXP file (which is CDBurnerXP's compilation format)
Then, you can use the following command
cdbxpcmd --burn-data -layout:mycompilation.dxp -iso:output.iso -format:iso
Blatant plug, but I've just released an alpha version of an OpenSource C# library that can create ISO files. Doesn't directly integrate with Nant, but you could wrap up the library to achieve that. Theres a sample app (ISOCreate) that creates ISOs from a directory structure, but this sample could also get you started:
CDBuilder builder = new CDBuilder();
builder.UseJoliet = true;
builder.VolumeIdentifier = "A_SAMPLE_DISK";
builder.AddFile(#"Folder\Hello.txt", Encoding.ASCII.GetBytes("Hello World!"));
builder.Build(#"C:\temp\sample.iso");
.NET DiscUtils (on GitHub)
Get mkisofs here - it is part of cdrtools. Available for most platforms.
USAGE examples:
mkisofs -v -dvd-video -V "VOLUME_NAME" -o "c:\my movies\iso\movie.iso" "c:\my movies\dvd"
mkisofs -r -R -J -l -L -o image-file.iso c:\project\install
Powershell can create an ISO. The below example includes a GUI. Credit to http://blog.apps.id.au/?p=5321
# Author: Hrisan Dzhankardashliyski
# Date: 20/05/2015
# Inspiration from
#
# http://blogs.msdn.com/b/opticalstorage/archive/2010/08/13/writing-optical-discs-using-imapi-2-in-powershell.aspx</a>
#
# and
#
# http://tools.start-automating.com/Install-ExportISOCommand/</a>
#
# with help from
#
# http://stackoverflow.com/a/9802807/223837</a>
$InputFolder = ""
function WriteIStreamToFile([__ComObject] $istream, [string] $fileName)
{
# NOTE: We cannot use [System.Runtime.InteropServices.ComTypes.IStream],
# since PowerShell apparently cannot convert an IStream COM object to this
# Powershell type. (See http://stackoverflow.com/a/9037299/223837 for
# details.)
#
# It turns out that .NET/CLR _can_ do this conversion.
#
# That is the reason why method FileUtil.WriteIStreamToFile(), below,
# takes an object, and casts it to an IStream, instead of directly
# taking an IStream inputStream argument.
$cp = New-Object CodeDom.Compiler.CompilerParameters
$cp.CompilerOptions = "/unsafe"
$cp.WarningLevel = 4
$cp.TreatWarningsAsErrors = $true
Add-Type -CompilerParameters $cp -TypeDefinition #"
using System;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
namespace My
{
public static class FileUtil {
public static void WriteIStreamToFile(object i, string fileName) {
IStream inputStream = i as IStream;
FileStream outputFileStream = File.OpenWrite(fileName);
int bytesRead = 0;
int offset = 0;
byte[] data;
do {
data = Read(inputStream, 2048, out bytesRead);
outputFileStream.Write(data, 0, bytesRead);
offset += bytesRead;
} while (bytesRead == 2048);
outputFileStream.Flush();
outputFileStream.Close();
}
unsafe static private byte[] Read(IStream stream, int toRead, out int read) {
byte[] buffer = new byte[toRead];
int bytesRead = 0;
int* ptr = &bytesRead;
stream.Read(buffer, toRead, (IntPtr)ptr);
read = bytesRead;
return buffer;
}
}
}
"#
[My.FileUtil]::WriteIStreamToFile($istream, $fileName)
}
# The Function defines the ISO parameturs and writes it to file
function createISO([string]$VolName,[string]$Folder,[bool]$IncludeRoot,[string]$ISOFile){
# Constants from http://msdn.microsoft.com/en-us/library/windows/desktop/aa364840.aspx
$FsiFileSystemISO9660 = 1
$FsiFileSystemJoliet = 2
$FsiFileSystemUDF = 4
$fsi = New-Object -ComObject IMAPI2FS.MsftFileSystemImage
#$fsi.FileSystemsToCreate = $FsiFileSystemISO9660 + $FsiFileSystemJoliet
$fsi.FileSystemsToCreate = $FsiFileSystemUDF
#When FreeMediaBlocks is set to 0 it allows the ISO file to be with unlimited size
$fsi.FreeMediaBlocks = 0
$fsi.VolumeName = $VolName
$fsi.Root.AddTree($Folder, $IncludeRoot)
WriteIStreamToFile $fsi.CreateResultImage().ImageStream $ISOFile
}
Function Get-Folder($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$foldername = New-Object System.Windows.Forms.FolderBrowserDialog
$foldername.rootfolder = "MyComputer"
if($foldername.ShowDialog() -eq "OK")
{
$folder += [string]$foldername.SelectedPath
}
return $folder
}
# Show an Open Folder Dialog and return the directory selected by the user.
function Read-FolderBrowserDialog([string]$Message, [string]$InitialDirectory, [switch]$NoNewFolderButton)
{
$browseForFolderOptions = 0
if ($NoNewFolderButton) { $browseForFolderOptions += 512 }
$app = New-Object -ComObject Shell.Application
$folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
if ($folder) { $selectedDirectory = $folder.Self.Path }
else { $selectedDirectory = '' }
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) > $null
return $selectedDirectory
}
#Prompts the user to save the ISO file, if the files does not exists it will create it otherwise overwrite without prompt
Function Get-SaveFile($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
$SaveFileDialog.CreatePrompt = $false
$SaveFileDialog.OverwritePrompt = $false
$SaveFileDialog.initialDirectory = $initialDirectory
$SaveFileDialog.filter = "ISO files (*.iso)| *.iso"
$SaveFileDialog.ShowHelp = $true
$SaveFileDialog.ShowDialog() | Out-Null
$SaveFileDialog.filename
}
# Show message box popup and return the button clicked by the user.
function Read-MessageBoxDialog([string]$Message, [string]$WindowTitle, [System.Windows.Forms.MessageBoxButtons]$Buttons = [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]$Icon = [System.Windows.Forms.MessageBoxIcon]::None)
{
Add-Type -AssemblyName System.Windows.Forms
return [System.Windows.Forms.MessageBox]::Show($Message, $WindowTitle, $Buttons, $Icon)
}
# GUI interface for the PowerShell script
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") #loading the necessary .net libraries (using void to suppress output)
$Form = New-Object System.Windows.Forms.Form #creating the form (this will be the "primary" window)
$Form.Text = "ISO Creator Tool:"
$Form.Size = New-Object System.Drawing.Size(600,300) #the size in px of the window length, height
$Form.FormBorderStyle = 'FixedDialog'
$Form.MaximizeBox = $false
$Form.MinimizeBox = $false
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(20,20)
$objLabel.Size = New-Object System.Drawing.Size(120,20)
$objLabel.Text = "Please select a Folder:"
$Form.Controls.Add($objLabel)
$InputBox = New-Object System.Windows.Forms.TextBox
$InputBox.Location = New-Object System.Drawing.Size(150,20)
$InputBox.Size = New-Object System.Drawing.Size(300,20)
$InputBox.Enabled = $false
$Form.Controls.Add($InputBox)
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(20,80)
$objLabel2.Size = New-Object System.Drawing.Size(120,20)
$objLabel2.Text = "ISO File Name:"
$Form.Controls.Add($objLabel2)
$InputBox2 = New-Object System.Windows.Forms.TextBox
$InputBox2.Location = New-Object System.Drawing.Size(150,80)
$InputBox2.Size = New-Object System.Drawing.Size(300,20)
$InputBox2.Enabled = $false
$Form.Controls.Add($InputBox2)
$objLabel3 = New-Object System.Windows.Forms.Label
$objLabel3.Location = New-Object System.Drawing.Size(20,50)
$objLabel3.Size = New-Object System.Drawing.Size(120,20)
$objLabel3.Text = "ISO Volume Name:"
$Form.Controls.Add($objLabel3)
$InputBox3 = New-Object System.Windows.Forms.TextBox
$InputBox3.Location = New-Object System.Drawing.Size(150,50)
$InputBox3.Size = New-Object System.Drawing.Size(150,20)
$Form.Controls.Add($InputBox3)
$objLabel4 = New-Object System.Windows.Forms.Label
$objLabel4.Location = New-Object System.Drawing.Size(20,120)
$objLabel4.Size = New-Object System.Drawing.Size(120,20)
$objLabel4.Text = "Status Msg:"
$Form.Controls.Add($objLabel4)
$InputBox4 = New-Object System.Windows.Forms.TextBox
$InputBox4.Location = New-Object System.Drawing.Size(150,120)
$InputBox4.Size = New-Object System.Drawing.Size(200,20)
$InputBox4.Enabled = $false
$InputBox4.Text = "Set ISO Parameters..."
$InputBox4.BackColor = "LimeGreen"
$Form.Controls.Add($InputBox4)
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(470,20)
$Button.Size = New-Object System.Drawing.Size(80,20)
$Button.Text = "Browse"
$Button.Add_Click({
$InputBox.Text=Read-FolderBrowserDialog
$InputBox4.Text = "Set ISO Parameters..."
})
$Form.Controls.Add($Button)
$Button2 = New-Object System.Windows.Forms.Button
$Button2.Location = New-Object System.Drawing.Size(470,120)
$Button2.Size = New-Object System.Drawing.Size(80,80)
$Button2.Text = "CreateISO"
$Button2.Add_Click({
if(($InputBox.Text -eq "") -or ($InputBox3.Text -eq "")){
Read-MessageBoxDialog "You have to select folder and specify ISO Volume Name" "Error: No Parameters entered!"
} else{
$SaveDialog = Get-SaveFile
#If you click cancel when save file dialog is called
if ($SaveDialog -eq ""){
return
}
$InputBox2.Text= $SaveDialog
$InputBox2.Refresh()
if($checkBox1.Checked){
$includeRoot=$true
}
else{
$includeRoot=$false
}
$InputBox4.BackColor = "Red"
$InputBox4.Text = "Generating ISO File!"
$InputBox4.Refresh()
createISO $InputBox3.Text $InputBox.Text $includeRoot $InputBox2.Text
$InputBox4.BackColor = "LimeGreen"
$InputBox4.Text = "ISO Creation Finished!"
$InputBox4.Refresh()
}
})
$Form.Controls.Add($Button2)
$objLabel5 = New-Object System.Windows.Forms.Label
$objLabel5.Location = New-Object System.Drawing.Size(20,160)
$objLabel5.Size = New-Object System.Drawing.Size(280,20)
$objLabel5.Text = "Check the box if you want to include the top folder:"
$Form.Controls.Add($objLabel5)
$checkBox1 = New-Object System.Windows.Forms.CheckBox
$checkBox1.Location = New-Object System.Drawing.Size(300,156)
$Form.Controls.Add($checkBox1)
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()
If you want to be Microsoft addictive (not install additional software). You can use IMAPI, build into Windows to burn images. Additional information regarding scripting IMAPI can be found in MSDN
I've used magiciso, but haven't tested it extensivly. (I may try some of the others mentioned here after some testing) I first make an installer (single file) then just make this an iso.
http://www.magiciso.com/
Here's the result of my struggle to get this working in python:
add_option = '-a'
add_option_value = installer_fullpath
response_option = '-py' # answer yes to all options
# Get the tempfile name -- to resolve long name issue
# --> My file names were initially too long for MagicIso and it would choke
f_handle = tempfile.TemporaryFile(suffix='.iso', prefix='mi_', dir='.')
temp_filename = f_handle.name
f_handle.close() # File automatically deleted on close
args = (magiciso_exe_fullpath,temp_filename,response_option,add_option,add_option_value)
# log output to file
magiciso_con_f = open(MAGICISO_CON_LOG,'w')
magiciso_process = subprocess.Popen(args,stdout=magiciso_con_f,stderr=magiciso_con_f)
magiciso_process.wait()
I am using mkisofs.exe from the installation kit of nLite or BartPE, from where I also learned the required parameters for building a bootable cd.