qshinoの日記

Powershell関係と徒然なこと

WPF 簡単文書表示/XPS

WPF簡単文書表示

Xamlを使って簡単に文書表示ウインドウを作る。主要なものは下記の3点。

  • コントロール: DocumentViewer
  • 文書: XPSファイル
  • 文書型: FixedDocument

使用する文書ファイルはXPS形式。MicroSoft版のPDFの様な もの。Wordで保存できる。また、プリンタとしてXPS Writerを選べば、印刷時にXPSに変換できる。文書の型はFixedDocument。表示はDocumentViewer。

早速Xaml

Xaml

DocumentViewerをStackPanelに配置しただけ。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Document" Width="100" Height="100">
  <StackPanel>
    <DocumentViewer Name="docViewer" />
  </StackPanel>
</Window>

コード

DocumentViewerのDocumentに読んだ文書を設定。

   private void LoadFileToDocumentViewer(string filename)
    {
      XpsDocument document = new XpsDocument(filename, FileAccess.Read, CompressionOption.NotCompressed);

      FixedDocumentSequence sequence = document.GetFixedDocumentSequence();

      docViewer.Document = sequence as IDocumentPaginatorSource;
    }

型など

  • System.Windows.Documents.IDocumentPaginatorSource
  • System.io.FileAccess
  • System.io.Packaging.CompressionOption
  • System.Windows.xps.Packaging.XpsDocument (ReachFramework)
  • System.Windows.Controls.DocumentViewer

参考

http://www.balard.sakura.ne.jp/vb/wpf/documentviewer.php

Msdn DocumentViewer class

https://msdn.microsoft.com/ja-jp/library/system.windows.controls.documentviewer(v=vs.110).aspx

サンプル

C#

http://www.kanazawa-net.ne.jp/~pmansato/xps/xps_viewer.htm

http://office-qa.com/Word/wd184.htm

StackOverFlow

http://stackoverflow.com/questions/5116465/integrating-help-in-a-wpf-application

VB

http://www.balard.sakura.ne.jp/vb/wpf/documentviewer.php

PDFViewer

https://codezine.jp/article/detail/7543

xpsdocument

https://msdn.microsoft.com/ja-jp/library/system.windows.xps.packaging.xpsdocument(v=vs.110).aspx

new-object

https://msdn.microsoft.com/ja-jp/powershell/scripting/getting-started/cookbooks/creating-.net-and-com-objects–new-object-

外部プログラム起動方法調査

外部プログラム起動方法調査

コンソールアプリを別ウインドウで非同期実行しつつ、標準出力を受け取る。

無理?

まずは外部プログラム実行方法

  1. & 同期
  2. start-process 非同期/同期(-wait)
  3. start-job 非同期
  4. System.Diagnostics.Process
  5. C#で書く?

期待値は、非同期でコンソール画面に表示しつつ、起動元が出力を受け取る方法。

取り敢えず、コンソールが表示されるものはStart-processだけだった様な。

Strart-job 内でStart-peocess -waitを呼ぶとどうなる?

結果は、コンソール表示される。

結局、コンソールアプリのコンソールウインドウを表示しつつとなると、Start-Processか.netのProcessを使うのが良さそう。

Start-Process

  • 同期/非同期両方可能
  • 終了コード入手可能
  • 標準入出力。ファイル指定可
  • コンソールウインドウ出る。消すことも可
  • Eventが4種類

イベント一覧

  1. OutputDataReceived
  2. ErrorDataReceived
  3. Disposed
  4. Exited

非同期実行がデフォルト。-waitで同期実行

終了コードは$proc = start-process -PassThruで起動し、終了後に$proc.ExitCode で受け取る。 終了確認は、event又は、$proc.HasExited にて確認。

標準入出力をファイルに設定出来る。

$proc

-PassThru時の戻り値

例 $proc = Start-Process a.bat -PassThru

型はProcess

プロパティ

  • HasExited
  • ExitCode

Start-Job

  • 同期/非同期両方可能
  • 終了コード、不明
  • 標準出力受け取れる
  • コンソールウインドウでない

&

  • 同期のみ
  • 終了コードは$LastExitCode
  • コンソールウインドウ表示されない
  • 標準出力受け取れる

System.Diagnostics.Process

ほぼ何でも出来る。標準出力もイベント処理も。つまり、ラストリゾート。

使用例

Start-processを使う。

function exec ($cmd, $onexit, $msg ){
  $proc = Start-Process $cmd -PassThru
  $exited = Register-ObjectEvent $proc -EventName Exited -Action $onexit -MessageData $msg
  return $proc, $exited
}

$cmd = "a.bat"
$onexit = {
  # $Sender, $Event, $EventSubscriber, $EventArgs, $Args

  Unregister-Event $EventSubscriber.name

  [Process]$proc = $Sender
  $msg = $event.MessageData
  Write-Host $msg

  $exitcode = $proc.ExitCode
  Write-Host "Exit with $exitcode"
}
$msg = "Konnichiwa"

$proc, $ev = exec $cmd $onexit $msg

Event引数

  1. $Sender
  2. $Event
  3. $EventSubscriber
  4. $EventArgs
  5. $Args

イベント引数整理

正しい?

  • $args = $event.SourceArgs
  • $EventArgs = $event.SourceEventArgs
  • $Sender = $event.Sender
  • $Event = PSEventArgs

上記から、独立変数は$Eventと$EventSubscriberの二つ。EventSubscriberは置いといて、$EventのクラスPSEventArgs

PSEventArgs

  • ComputerName
  • EventIdentifier
  • RunSpaceId
  • SourceIdentifier
  • Sender
  • SourceArgs
  • SourceEventArgs
  • MessageData
  • TimeGenerated

SourceArgs

SourceArgs Gets any original arguments of the event that were captured and passed on by the source when the event was raised. This property is introduced in Windows PowerShell 2.0.

SourceEventArgs

SourceEventArgs Gets any arguments that were added by the source when the event was raised. This property is introduced in Windows PowerShell 2.0.

参考

https://msdn.microsoft.com/ja-jp/library/system.management.automation.pseventargs(v=vs.85).aspx

$ARGS

Contains an array of the undeclared parameters and\/or parameter values that are passed to a function, script, or script block. When you create a function, you can declare the parameters by using the param keyword or by adding a comma-separated list of parameters in parentheses after the function name.

In an event action, the $Args variable contains objects that represent the event arguments of the event that is being processed. This variable is populated only within the Action block of an event registration command. The value of this variable can also be found in the SourceArgs property of the PSEventArgs object (System.Management.Automation.PSEventArgs) that Get-Event returns.

$EVENT

Contains a PSEventArgs object that represents the event that is being processed. This variable is populated only within the Action block of an event registration command, such as Register-ObjectEvent. The value of this variable is the same object that the Get-Event cmdlet returns. Therefore, you can use the properties of the $Event variable, such as $Event.TimeGenerated , in an Action script block.

$EVENTARGS

Contains an object that represents the first event argument that derives from EventArgs of the event that is being processed. This variable is populated only within the Action block of an event registration command. The value of this variable can also be found in the SourceEventArgs property of the PSEventArgs (System.Management.Automation.PSEventArgs) object that Get-Event returns.

$EVENTSUBSCRIBER

Contains a PSEventSubscriber object that represents the event subscriber of the event that is being processed. This variable is populated only within the Action block of an event registration command. The value of this variable is the same object that the Get-EventSubscriber cmdlet returns.

$SENDER

Contains the object that generated this event. This variable is populated only within the Action block of an event registration command. The value of this variable can also be found in the Sender property of the PSEventArgs (System.Management.Automation.PSEventArgs) object that Get-Event returns.

ErrorDataReceived Event

Processのイベント

DataReceivedEventArgsクラスは、たった一つのData プロパティを持つ。

Exited イベントのEventArgsは空。

例) DataReceivedEvent

// Define the namespaces used by this sample.
using System;
using System.Text;
using System.Globalization;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.ComponentModel;


namespace ProcessAsyncStreamSamples
{

    class ProcessNetStreamRedirection
    {
        // Define static variables shared by class methods.
        private static StreamWriter streamError =null;
        private static String netErrorFile = "";
        private static StringBuilder netOutput = null;
        private static bool errorRedirect = false;
        private static bool errorsWritten = false;

        public static void RedirectNetCommandStreams()
        {
            String netArguments;
            Process netProcess;

            // Get the input computer name.
            Console.WriteLine("Enter the computer name for the net view command:");
            netArguments = Console.ReadLine().ToUpper(CultureInfo.InvariantCulture);
            if (String.IsNullOrEmpty(netArguments))
            {
                // Default to the help command if there is not an input argument.
                netArguments = "/?";
            }

            // Check if errors should be redirected to a file.
            errorsWritten = false;
            Console.WriteLine("Enter a fully qualified path to an error log file");
            Console.WriteLine("  or just press Enter to write errors to console:");
            netErrorFile = Console.ReadLine().ToUpper(CultureInfo.InvariantCulture);
            if (!String.IsNullOrEmpty(netErrorFile))
            {
                errorRedirect = true;
            }

            // Note that at this point, netArguments and netErrorFile
            // are set with user input.  If the user did not specify
            // an error file, then errorRedirect is set to false.

            // Initialize the process and its StartInfo properties.
            netProcess = new Process();
            netProcess.StartInfo.FileName = "Net.exe";

            // Build the net command argument list.
            netProcess.StartInfo.Arguments = String.Format("view {0}", 
                netArguments);

            // Set UseShellExecute to false for redirection.
            netProcess.StartInfo.UseShellExecute = false;

            // Redirect the standard output of the net command.  
            // This stream is read asynchronously using an event handler.
            netProcess.StartInfo.RedirectStandardOutput = true;
            netProcess.OutputDataReceived += new DataReceivedEventHandler(NetOutputDataHandler);
            netOutput = new StringBuilder();

            if (errorRedirect)
            {
                // Redirect the error output of the net command. 
                netProcess.StartInfo.RedirectStandardError = true;
                netProcess.ErrorDataReceived += new DataReceivedEventHandler(NetErrorDataHandler);
            }
            else 
            {
                // Do not redirect the error output.
                netProcess.StartInfo.RedirectStandardError = false;
            }

            Console.WriteLine("\nStarting process: net {0}", 
                netProcess.StartInfo.Arguments);
            if (errorRedirect)
            {
                Console.WriteLine("Errors will be written to the file {0}", 
                    netErrorFile);
            }

            // Start the process.
            netProcess.Start();

            // Start the asynchronous read of the standard output stream.
            netProcess.BeginOutputReadLine();

            if (errorRedirect)
            {
                // Start the asynchronous read of the standard
                // error stream.
                netProcess.BeginErrorReadLine();
            }

            // Let the net command run, collecting the output.
            netProcess.WaitForExit();

            if (streamError != null)
            {
                // Close the error file.
                streamError.Close();
            }
            else 
            {
                // Set errorsWritten to false if the stream is not
                // open.   Either there are no errors, or the error
                // file could not be opened.
                errorsWritten = false;
            }

            if (netOutput.Length > 0)
            {
                // If the process wrote more than just
                // white space, write the output to the console.
                Console.WriteLine("\nPublic network shares from net view:\n{0}\n", 
                    netOutput);
            }

            if (errorsWritten)
            {
                // Signal that the error file had something 
                // written to it.
                String [] errorOutput = File.ReadAllLines(netErrorFile);
                if (errorOutput.Length > 0)
                {
                    Console.WriteLine("\nThe following error output was appended to {0}.",
                        netErrorFile);
                    foreach (String errLine in errorOutput)
                    {
                        Console.WriteLine("  {0}", errLine);
                    }
                }
                Console.WriteLine();
            }

            netProcess.Close();

        }

        private static void NetOutputDataHandler(object sendingProcess, 
            DataReceivedEventArgs outLine)
        {
            // Collect the net view command output.
            if (!String.IsNullOrEmpty(outLine.Data))
            {
                // Add the text to the collected output.
                netOutput.Append(Environment.NewLine + "  " + outLine.Data);
            }
        }

        private static void NetErrorDataHandler(object sendingProcess, 
            DataReceivedEventArgs errLine)
        {
            // Write the error text to the file if there is something
            // to write and an error file has been specified.

            if (!String.IsNullOrEmpty(errLine.Data))
            {
                if (!errorsWritten)
                {
                    if (streamError == null)
                    {
                        // Open the file.
                        try 
                        {
                            streamError = new StreamWriter(netErrorFile, true);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("Could not open error file!");
                            Console.WriteLine(e.Message.ToString());
                        }
                    }

                    if (streamError != null)
                    {
                        // Write a header to the file if this is the first
                        // call to the error output handler.
                        streamError.WriteLine();
                        streamError.WriteLine(DateTime.Now.ToString());
                        streamError.WriteLine("Net View error output:");
                    }
                    errorsWritten = true;
                }

                if (streamError != null)
                {
                    // Write redirected errors to the file.
                    streamError.WriteLine(errLine.Data);
                    streamError.Flush();
                }
            }
        }
    }
} 

Exited event

using System;
using System.Diagnostics;
using System.Threading;

class PrintProcessClass
{

    private Process myProcess = new Process();
    private int elapsedTime;
    private bool eventHandled;

    // Print a file with any known extension.
    public void PrintDoc(string fileName)
    {

        elapsedTime = 0;
        eventHandled = false;

        try
        {
            // Start a process to print a file and raise an event when done.
            myProcess.StartInfo.FileName = fileName;
            myProcess.StartInfo.Verb = "Print";
            myProcess.StartInfo.CreateNoWindow = true;
            myProcess.EnableRaisingEvents = true;
            myProcess.Exited += new EventHandler(myProcess_Exited);
            myProcess.Start();

        }
        catch (Exception ex)
        {
            Console.WriteLine("An error occurred trying to print \"{0}\":" + "\n" + ex.Message, fileName);
            return;
        }

        // Wait for Exited event, but not more than 30 seconds.
        const int SLEEP_AMOUNT = 100;
        while (!eventHandled)
        {
            elapsedTime += SLEEP_AMOUNT;
            if (elapsedTime > 30000)
            {
                break;
            }
            Thread.Sleep(SLEEP_AMOUNT);
        }
    }

    // Handle Exited event and display process information.
    private void myProcess_Exited(object sender, System.EventArgs e)
    {

        eventHandled = true;
        Console.WriteLine("Exit time:    {0}\r\n" +
            "Exit code:    {1}\r\nElapsed time: {2}", myProcess.ExitTime, myProcess.ExitCode, elapsedTime);
    }

    public static void Main(string[] args)
    {

        // Verify that an argument has been entered.
        if (args.Length <= 0)
        {
            Console.WriteLine("Enter a file name.");
            return;
        }

        // Create the process and print the document.
        PrintProcessClass myPrintProcess = new PrintProcessClass();
        myPrintProcess.PrintDoc(args[0]);
    }
}

参考

Start-Process

https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.management/start-process

start-job http://trivial-contents.com/programming/powershell/parallel.html

start-job http://mtgpowershell.blogspot.jp/2010/11/blog-post_21.html?m=1

-PassThru

http://tech.guitarrapc.com/entry/2013/01/24/200115

Process classを使用する。

http://ja.stackoverflow.com/questions/19110/powershellにて外部プロセスによる大量の標準出力を出力順に取得したい

Process class http://tech.guitarrapc.com/entry/2014/12/14/075248

Msdn process clasd https://msdn.microsoft.com/ja-jp/library/system.diagnostics.process(v=vs.110).aspx#イベント

StackOverflow

http://stackoverflow.com/questions/651223/powershell-start-process-and-cmdline-switches

Register-ObjectEvent

https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.utility/register-objectevent

PS自動変数

https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables

お勉強用

tech.guitarrapさんのInvoke-Process

Event周りを確認。$Event.MessageDataは、Register-ObjectEvent -MessageData DATAで渡す。残りの$SourceArgs.Dataと、$SourceEventArgs.Dataが不明。

取り敢えず、JobのEvent引数。これがProcessのExited Eventで成り立つ?

  1. $Sender
  2. $Event
  3. $EventSubscriber
  4. $SourceEventArgs
  5. $SourceArgs
function Invoke-Process
{
    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $false, Position = 0)]
        [string]$FileName = "PowerShell.exe",

        [Parameter(Mandatory = $false, Position = 1)]
        [string]$Arguments = "",
        
        [Parameter(Mandatory = $false, Position = 2)]
        [string]$WorkingDirectory = ".",

        [Parameter(Mandatory = $false, Position = 3)]
        [TimeSpan]$Timeout = [System.TimeSpan]::FromMinutes(2),

        [Parameter(Mandatory = $false, Position = 4)]
        [System.Diagnostics.ProcessPriorityClass]$Priority = [System.Diagnostics.ProcessPriorityClass]::Normal
    )

    end
    {
        try
        {
            # new Process
            $process = NewProcess -FileName $FileName -Arguments $Arguments -WorkingDirectory $WorkingDirectory
            
            # Event Handler for Output
            $stdSb = New-Object -TypeName System.Text.StringBuilder
            $errorSb = New-Object -TypeName System.Text.StringBuilder
            $scripBlock = 
            {
                $x = $Event.SourceEventArgs.Data
                if (-not [String]::IsNullOrEmpty($x))
                {
                    [System.Console]::WriteLine($x)
                    $Event.MessageData.AppendLine($x)
                }
            }
            $stdEvent = Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -Action $scripBlock -MessageData $stdSb
            $errorEvent = Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -Action $scripBlock -MessageData $errorSb

            # execution
            $process.Start() > $null
            $process.PriorityClass = $Priority
            $process.BeginOutputReadLine()
            $process.BeginErrorReadLine()
            
            # wait for complete
            "Waiting for command complete. It will Timeout in {0}ms" -f $Timeout.TotalMilliseconds | VerboseOutput
            $isTimeout = $false
            if (-not $Process.WaitForExit($Timeout.TotalMilliseconds))
            {
                $isTimeout = $true
                "Timeout detected for {0}ms. Kill process immediately" -f $Timeout.TotalMilliseconds | VerboseOutput
                $Process.Kill()
            }
            $Process.WaitForExit()
            $Process.CancelOutputRead()
            $Process.CancelErrorRead()

            # verbose Event Result
            $stdEvent, $errorEvent | VerboseOutput

            # Unregister Event to recieve Asynchronous Event output (You should call before process.Dispose())
            Unregister-Event -SourceIdentifier $stdEvent.Name
            Unregister-Event -SourceIdentifier $errorEvent.Name

            # verbose Event Result
            $stdEvent, $errorEvent | VerboseOutput

            # Get Process result
            return GetCommandResult -Process $process -StandardStringBuilder $stdSb -ErrorStringBuilder $errorSb -IsTimeOut $isTimeout
        }
        finally
        {
            if ($null -ne $process){ $process.Dispose() }
            if ($null -ne $stdEvent){ $stdEvent.StopJob(); $stdEvent.Dispose() }
            if ($null -ne $errorEvent){ $errorEvent.StopJob(); $errorEvent.Dispose() }
        }
    }

    begin
    {
        function NewProcess
        {
            [OutputType([System.Diagnostics.Process])]
            [CmdletBinding()]
            param
            (
                [parameter(Mandatory = $true)]
                [string]$FileName,
                
                [parameter(Mandatory = $false)]
                [string]$Arguments,
                
                [parameter(Mandatory = $false)]
                [string]$WorkingDirectory
            )

            "Execute command : '{0} {1}', WorkingSpace '{2}'" -f $FileName, $Arguments, $WorkingDirectory | VerboseOutput
            # ProcessStartInfo
            $psi = New-object System.Diagnostics.ProcessStartInfo 
            $psi.CreateNoWindow = $true
            $psi.LoadUserProfile = $true
            $psi.UseShellExecute = $false
            $psi.RedirectStandardOutput = $true
            $psi.RedirectStandardError = $true
            $psi.FileName = $FileName
            $psi.Arguments+= $Arguments
            $psi.WorkingDirectory = $WorkingDirectory

            # Set Process
            $process = New-Object System.Diagnostics.Process 
            $process.StartInfo = $psi
            $process.EnableRaisingEvents = $true
            return $process
        }

        function GetCommandResult
        {
            [OutputType([PSCustomObject])]
            [CmdletBinding()]
            param
            (
                [parameter(Mandatory = $true)]
                [System.Diagnostics.Process]$Process,

                [parameter(Mandatory = $true)]
                [System.Text.StringBuilder]$StandardStringBuilder,

                [parameter(Mandatory = $true)]
                [System.Text.StringBuilder]$ErrorStringBuilder,

                [parameter(Mandatory = $true)]
                [Bool]$IsTimeout
            )
            
            'Get command result string.' | VerboseOutput
            return [PSCustomObject]@{
                StandardOutput = $StandardStringBuilder.ToString().Trim()
                ErrorOutput = $ErrorStringBuilder.ToString().Trim()
                ExitCode = $Process.ExitCode
                IsTimeOut = $IsTimeout
            }
        }

        filter VerboseOutput
        {
            $_ | Out-String -Stream | Write-Verbose
        }
    }
}

WPF依存プロパティ

WPF依存プロパティ

依存プロパティ関連をこれから調査。

バインディングの際のターゲットに指定できる。バインディングに関しては別途。

参考

https://garafu.blogspot.jp/2016/01/wpf-dependencyproperty.html?m=1

http://www.atmarkit.co.jp/ait/spv/1010/08/news123.html