Skip to content

Commit

Permalink
Add XML highlighting, "Run as Admin" option, and CSV export (Replaces #…
Browse files Browse the repository at this point in the history
…66) (#69)

* Add XML highlighting, "Run as Admin" option, and CSV export

---------

Co-authored-by: Joey <Ceus@users.noreply.github.com>
  • Loading branch information
kmaki565 and Ceus authored Aug 24, 2024
1 parent bc8b534 commit 7875604
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 16 deletions.
6 changes: 6 additions & 0 deletions EventLook/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
<setting name="ShowsRecordId" serializeAs="String">
<value>False</value>
</setting>
<setting name="WinWidth" serializeAs="String">
<value>1100</value>
</setting>
<setting name="WinHeight" serializeAs="String">
<value>640</value>
</setting>
</EventLook.Properties.Settings>
</userSettings>
</configuration>
3 changes: 3 additions & 0 deletions EventLook/Model/LogSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

namespace EventLook.Model;

/// <summary>
/// Represents a log source, which can be a .evtx file or a local Event Log Channel.
/// </summary>
public class LogSource
{
public LogSource(string path, PathType type = PathType.LogName)
Expand Down
16 changes: 16 additions & 0 deletions EventLook/Model/TextHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ public static bool IsTextMatched(string target, AndSearchMaterial andSearch)
return andSearch.WordsToInclude.All(x => target.Contains(x, StringComparison.OrdinalIgnoreCase))
&& andSearch.WordsToExclude.All(x => !target.Contains(x, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
/// Escapes special characters in a CSV value.
/// </summary>
public static string EscapeCsvValue(string value)
{
if (value.Contains("\""))
{
value = value.Replace("\"", "\"\"");
}
if (value.Contains(",") || value.Contains("\n") || value.Contains("\r"))
{
value = $"\"{value}\"";
}
return value;
}
}

public class AndSearchMaterial
Expand Down
26 changes: 25 additions & 1 deletion EventLook/Properties/Settings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions EventLook/Properties/Settings.settings
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,11 @@
<Setting Name="ShowsRecordId" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="WinWidth" Type="System.Int32" Scope="User">
<Value Profile="(Default)">1100</Value>
</Setting>
<Setting Name="WinHeight" Type="System.Int32" Scope="User">
<Value Profile="(Default)">640</Value>
</Setting>
</Settings>
</SettingsFile>
10 changes: 5 additions & 5 deletions EventLook/View/DetailWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EventLook.View" xmlns:viewmodel="clr-namespace:EventLook.ViewModel" d:DataContext="{d:DesignInstance Type=viewmodel:DetailViewModel}"
mc:Ignorable="d"
FocusManager.FocusedElement="{Binding ElementName=TextBoxXml}"
Title="Event Details" Height="530" Width="900">
FocusManager.FocusedElement="{Binding ElementName=RichTextBoxXml}"
Title="Event Details" Height="550" Width="900">
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Command="{Binding UpCommand}" FontFamily="Segoe UI Symbol" Content="&#xE110;" FontSize="15" Width="30" Height="30" Margin="10"/>
<Button Command="{Binding DownCommand}" FontFamily="Segoe UI Symbol" Content="&#xE1FD;" FontSize="15" Width="30" Height="30" Margin="10"/>
</StackPanel>
<TextBox Name="TextBoxXml" Text="{Binding EventXml, Mode=OneWay}"
IsReadOnly="True" IsReadOnlyCaretVisible="True"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="10,10,0,10"/>
<RichTextBox Name="RichTextBoxXml" FontSize="13"
local:TextBoxBehavior.HighlightedXml="{Binding EventXml, Mode=OneWay}"
IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="10,10,0,10"/>
</DockPanel>
</Grid>
</Window>
19 changes: 15 additions & 4 deletions EventLook/View/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:v="clr-namespace:EventLook.View"
xmlns:viewmodel="clr-namespace:EventLook.ViewModel"
xmlns:properties="clr-namespace:EventLook.Properties"
d:DataContext="{d:DesignInstance Type=viewmodel:MainViewModel}"
mc:Ignorable="d"
ResizeMode="CanResizeWithGrip"
PreviewKeyDown="Window_PreviewKeyDown" d:DesignHeight="640" d:DesignWidth="1100">
PreviewKeyDown="Window_PreviewKeyDown"
d:DesignHeight="640"
d:DesignWidth="1100"
Height="{Binding Source={x:Static properties:Settings.Default}, Path=WinHeight, Mode=TwoWay}"
Width="{Binding Source={x:Static properties:Settings.Default}, Path=WinWidth, Mode=TwoWay}"
WindowStartupLocation="CenterScreen"
Closing="Window_Closing">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
Expand Down Expand Up @@ -50,15 +57,19 @@
<KeyBinding Key="F5" Modifiers="Shift" Command="{Binding CancelCommand}"/>
<KeyBinding Key="L" Modifiers="Ctrl" Command="{Binding OpenLogPickerCommand}"/>
<KeyBinding Key="O" Modifiers="Ctrl" Command="{Binding OpenFileCommand}"/>
<KeyBinding Key="S" Modifiers="Ctrl" Command="{Binding ExportToCsvCommand}"/>
</Window.InputBindings>

<DockPanel>
<Menu DockPanel.Dock="Top" Background="White">
<MenuItem Header="_File">
<MenuItem Header="Open Event _Log on local computer" InputGestureText="Ctrl+L" Command="{Binding OpenLogPickerCommand}"/>
<MenuItem Header="_Open .evtx File" InputGestureText="Ctrl+O" Command="{Binding OpenFileCommand}"/>
<MenuItem Header="Export to C_SV file" InputGestureText="Ctrl+S" Command="{Binding ExportToCsvCommand}"/>
<Separator />
<MenuItem Header="Launch Windows _Event Viewer" Command="{Binding LaunchEventViewerCommand}"/>
<MenuItem Header="Restart app as _Administrator" Command="{Binding RunAsAdminCommand}" Visibility="{Binding IsRunAsAdmin, Converter={StaticResource InverseBooleanToVisibilityConverter}}"/>
<Separator />
<MenuItem Header="_Launch Windows Event Viewer" Command="{Binding LaunchEventViewerCommand}"/>
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="_Options">
Expand Down Expand Up @@ -158,7 +169,7 @@

<Expander x:Name="Ex1" Header="F_ilters" Grid.Row="1" Margin="10,0,0,0" ExpandDirection="Down">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Source: " Margin="5,0,0,0" VerticalAlignment="Center"/>
<TextBlock Text="Provider: " Margin="5,0,0,0" VerticalAlignment="Center"/>
<xctk:CheckComboBox Width="250"
IsSelectAllActive="True" IsAllItemsSelectedContentActive="True"
Delimiter=", "
Expand Down Expand Up @@ -245,7 +256,7 @@
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTextColumn Header="Source" Binding="{Binding Record.ProviderName}" Width="200">
<DataGridTextColumn Header="Provider" Binding="{Binding Record.ProviderName}" Width="200">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Style.Triggers>
Expand Down
6 changes: 6 additions & 0 deletions EventLook/View/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using AboutBoxWpf;
using CommunityToolkit.Mvvm.Messaging;
using EventLook.Model;
using EventLook.Properties;
using EventLook.ViewModel;
using System;
using System.IO;
Expand Down Expand Up @@ -48,6 +49,11 @@ public MainWindow()
ProcessCommandLine();
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Settings.Default.Save();
}

private void ProcessCommandLine()
{
var args = Environment.GetCommandLineArgs();
Expand Down
162 changes: 156 additions & 6 deletions EventLook/View/TextBoxBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Linq;

namespace EventLook.View;

Expand Down Expand Up @@ -53,4 +51,156 @@ public static bool GetEscapeClearsText(DependencyObject dependencyObject)
}

#endregion Attached Property EscapeClearsText

#region Attached Property HighlightedXml

// DependencyProperty for HighlightedXml
public static readonly DependencyProperty HighlightedXmlProperty =
DependencyProperty.RegisterAttached(
"HighlightedXml",
typeof(string),
typeof(TextBoxBehavior),
new FrameworkPropertyMetadata(string.Empty, OnHighlightedXmlChanged)
);

/// <summary>
/// Sets the HighlightedXml property on a DependencyObject.
/// </summary>
public static void SetHighlightedXml(DependencyObject element, string value)
{
element.SetValue(HighlightedXmlProperty, value);
}

/// <summary>
/// Gets the HighlightedXml property from a DependencyObject.
/// </summary>
public static string GetHighlightedXml(DependencyObject element)
{
return (string)element.GetValue(HighlightedXmlProperty);
}

/// <summary>
/// Called when HighlightedXml property changes.
/// </summary>
private static void OnHighlightedXmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is RichTextBox richTextBox && e.NewValue is string xml)
{
// Highlight XML content in RichTextBox
HighlightXml(richTextBox, xml);
}
}

/// <summary>
/// Highlights XML in a RichTextBox.
/// </summary>
private static void HighlightXml(RichTextBox richTextBox, string xml)
{
var doc = richTextBox.Document;
doc.Blocks.Clear(); // Clear existing content

// Format the XML string with proper indentation
var formattedXml = FormatXml(xml);

var paragraph = new Paragraph();
var xmlDoc = XDocument.Parse(formattedXml);

// Add the XML declaration
var declaration = xmlDoc.Declaration;
if (declaration != null)
{
paragraph.Inlines.Add(new Run($"<?xml version=\"{declaration.Version}\" encoding=\"{declaration.Encoding}\"?>") { Foreground = Brushes.SlateGray });
paragraph.Inlines.Add(new LineBreak());
}

// Add XML elements to paragraph
AddElementToParagraph(paragraph, xmlDoc.Root, 0);
doc.Blocks.Add(paragraph);
}

/// <summary>
/// Adds an XElement to a Paragraph with proper formatting.
/// </summary>
private static void AddElementToParagraph(Paragraph paragraph, XElement element, int indentLevel)
{
var indent = new string(' ', indentLevel * 2);
paragraph.Inlines.Add(new Run(indent));

var prefix = element.GetPrefixOfNamespace(element.Name.Namespace);
var elementName = !string.IsNullOrEmpty(prefix) ? $"{prefix}:{element.Name.LocalName}" : element.Name.LocalName;
paragraph.Inlines.Add(new Run($"<{elementName}") { Foreground = Brushes.MediumBlue });

// Add attributes
foreach (var attribute in element.Attributes())
{
paragraph.Inlines.Add(new Run($" {attribute.Name}") { Foreground = Brushes.Firebrick });
paragraph.Inlines.Add(new Run("=") { Foreground = Brushes.SlateGray });
paragraph.Inlines.Add(new Run($"\"{attribute.Value}\"") { Foreground = Brushes.ForestGreen });
}

if (element.IsEmpty || string.IsNullOrWhiteSpace(element.Value))
{
// Self-closing tag
paragraph.Inlines.Add(new Run(" />") { Foreground = Brushes.MediumBlue });
paragraph.Inlines.Add(new LineBreak());
}
else
{
paragraph.Inlines.Add(new Run(">") { Foreground = Brushes.MediumBlue });

if (!string.IsNullOrWhiteSpace(element.Value) && !element.HasElements)
{
// Add element value
if (element.Value.Contains("\n"))
{
paragraph.Inlines.Add(new LineBreak());
paragraph.Inlines.Add(new Run($"{new string(' ', (indentLevel + 1) * 2)}{element.Value}") { Foreground = Brushes.Black });
paragraph.Inlines.Add(new LineBreak());
paragraph.Inlines.Add(new Run($"{indent}</{elementName}>") { Foreground = Brushes.MediumBlue });
paragraph.Inlines.Add(new LineBreak());
}
else
{
paragraph.Inlines.Add(new Run(element.Value) { Foreground = Brushes.Black });
paragraph.Inlines.Add(new Run($"</{elementName}>") { Foreground = Brushes.MediumBlue });
paragraph.Inlines.Add(new LineBreak());
}
}
else
{
// Add child elements
paragraph.Inlines.Add(new LineBreak());
foreach (var childElement in element.Elements())
{
AddElementToParagraph(paragraph, childElement, indentLevel + 1);
}
paragraph.Inlines.Add(new Run($"{indent}</{elementName}>") { Foreground = Brushes.MediumBlue });
paragraph.Inlines.Add(new LineBreak());
}
}
}

/// <summary>
/// Formats the XML string with proper indentation.
/// </summary>
private static string FormatXml(string xml)
{
var stringBuilder = new System.Text.StringBuilder();
var element = XElement.Parse(xml);

using (var writer = System.Xml.XmlWriter.Create(stringBuilder, new System.Xml.XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = false,
NewLineOnAttributes = false
}))
{
element.Save(writer);
}

return stringBuilder.ToString();
}


#endregion Attached Property HighlightedXml
}
Loading

0 comments on commit 7875604

Please sign in to comment.