├── .gitignore ├── diff-word.cmd ├── docs └── ui-screenshot.png ├── diff-word-wrapper.cmd ├── start-gui.cmd ├── .editorconfig ├── Folder.DotSettings ├── License.md ├── Diff-Word.ps1 ├── CHANGELOG.md ├── Readme.md └── Gui-Diff-Word.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | -------------------------------------------------------------------------------- /diff-word.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | powershell.exe -File "%~dpn0.ps1" %* 3 | -------------------------------------------------------------------------------- /docs/ui-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForNeVeR/ExtDiff/HEAD/docs/ui-screenshot.png -------------------------------------------------------------------------------- /diff-word-wrapper.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Pass old-file new-file to script 3 | %~dp0/diff-word.cmd %2 %5 4 | -------------------------------------------------------------------------------- /start-gui.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmd /c start /min "" powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File "Gui-Diff-Word.ps1" 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /Folder.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (C) 2024 by ExtDiff contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Diff-Word.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $BaseFileName, 3 | [string] $ChangedFileName 4 | ) 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | function resolve($relativePath) { 9 | (Resolve-Path $relativePath).Path 10 | } 11 | 12 | $BaseFileName = resolve $BaseFileName 13 | $ChangedFileName = resolve $ChangedFileName 14 | 15 | # Remove the readonly attribute because Word is unable to compare readonly 16 | # files: 17 | $baseFile = Get-ChildItem $BaseFileName 18 | if ($baseFile.IsReadOnly) { 19 | $baseFile.IsReadOnly = $false 20 | } 21 | 22 | # Constants 23 | $wdDoNotSaveChanges = 0 24 | $wdCompareTargetNew = 2 25 | 26 | try { 27 | $word = New-Object -ComObject Word.Application 28 | $word.Visible = $true 29 | $document = $word.Documents.Open($BaseFileName, $false, $false) 30 | $document.Compare($ChangedFileName, [ref]"Comparison", [ref]$wdCompareTargetNew, [ref]$true, [ref]$true) 31 | 32 | $word.ActiveDocument.Saved = 1 33 | 34 | # Now close the document so only compare results window persists: 35 | $document.Close([ref]$wdDoNotSaveChanges) 36 | } catch { 37 | Add-Type -AssemblyName System.Windows.Forms 38 | [System.Windows.Forms.MessageBox]::Show($_.Exception) 39 | } 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.4.0] - 2024-10-19 8 | ### Added 9 | - Support for dropping of multiple files into either file box. Thanks to @coor for the contribution. 10 | 11 | ### Changed 12 | - The `start-gui.cmd` script no longer waits for the GUI process termination. Thanks to @coor for the contribution. 13 | 14 | ## [1.3.0] - 2024-01-15 15 | ### Added 16 | - A new UI form implementation. Thanks to @devegied-lamabpo for the contribution. 17 | 18 | ## [1.2.0] - 2017-11-13 19 | ### Added 20 | - A new wrapper batch file, `diff-word-wrapper.cmd`, for more convenient usage with Git. Thanks to @jonmseaman for the contribution. 21 | 22 | ## [1.1.0] - 2017-09-12 23 | ### Added 24 | - A new proxy batch file, `diff-word.cmd`, for more convenient execution in certain environments. Thanks to @jtpereyda for the contribution. 25 | 26 | ## [1.0.1] - 2017-07-21 27 | ### Added 28 | - The file paths are now resolved before passing them to Microsoft Word. This allows passing of the PowerShell paths, e.g., using `~` to denote the home directory. 29 | 30 | ## [1.0.0] - 2016-08-23 31 | The initial project release. Includes the only PowerShell script that calls Microsoft Word to compare two documents. 32 | 33 | [1.0.0]: https://github.com/ForNeVeR/ExtDiff/releases/tag/1.0 34 | [1.0.1]: https://github.com/ForNeVeR/ExtDiff/compare/1.0...1.0.1 35 | [1.1.0]: https://github.com/ForNeVeR/ExtDiff/compare/1.0.1...1.1.0 36 | [1.2.0]: https://github.com/ForNeVeR/ExtDiff/compare/1.1.0...1.2.0 37 | [1.3.0]: https://github.com/ForNeVeR/ExtDiff/compare/1.2.0...v1.3.0 38 | [1.4.0]: https://github.com/ForNeVeR/ExtDiff/compare/v1.3.0...v1.4.0 39 | [Unreleased]: https://github.com/ForNeVeR/ExtDiff/compare/v1.4.0...HEAD 40 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ExtDiff [![Status Aquana][status-aquana]][andivionian-status-classifier] 2 | ======= 3 | 4 | This is a small command line script that will compare two files using Microsoft 5 | Word file comparison tool. Microsoft Word will be started using COM automation. 6 | 7 | It is useful as a diff tool for Word-related file types. 8 | 9 | ## Using via command line 10 | 11 | To run the script, execute it through PowerShell like this: 12 | 13 | ```console 14 | $ powershell -File Diff-Word.ps1 oldfile.docx newfile.docx 15 | ``` 16 | 17 | Or via the batch file: 18 | 19 | ```console 20 | $ diff-word.cmd oldfile.docx newfile.docx 21 | ``` 22 | 23 | ## Using the GUI form 24 | 25 | ![UI Form Screenshot][docs.ui-form] 26 | 27 | To run the GUI form, run the script `start-gui.cmd`: it will open a form that stays on top of all the system windows, 28 | and you can drag files from Windows Explorer to two text fields 29 | (first for the old document, and second for the revised document). 30 | When you click the **Compare** button, it will start Word application with chosen documents as in command line usage, 31 | and the form will lose the "stay on top of all windows" behavior. 32 | It will regain this property again after the button **Clear** is clicked. 33 | 34 | If you drop multiple files into either box, then the first element will be placed in the box where files are dropped, and the second one into the other box. 35 | 36 | ## Using via Git Integration 37 | 38 | You can also use this tool with git, so that `git diff` will use Microsoft Word 39 | to diff `*.docx` files. 40 | 41 | To do this, you must configure your `.gitattributes` and `.gitconfig` to support 42 | a custom diff tool. 43 | 44 | ### `.gitattributes` 45 | 46 | To configure your `.gitattributes`, open or create a file called 47 | `.gitattributes` in your git repo's root directory. Add the following text to a 48 | new line in this file: 49 | 50 | ``` 51 | *.docx diff=word 52 | ``` 53 | 54 | It is also possible to create a global `.gitattributes` file that will be 55 | applied to every repository in a system. To do that, create a file 56 | `.gitattributes` in your home directory, and then perform the following command: 57 | 58 | ```console 59 | git config --global core.attributesfile ~/.gitattributes 60 | ``` 61 | 62 | ### `.gitconfig` 63 | 64 | To configure your `.gitconfig`, open or create the file in your home directory. 65 | Then, add the following to your `.gitconfig`: 66 | 67 | ```ini 68 | [diff "word"] 69 | command = /diff-word-wrapper.cmd 70 | ``` 71 | 72 | Replace `` with the path to this repo's 73 | location on disk. 74 | 75 | ------- 76 | 77 | Idea taken from [TortoiseSVN diff-doc script][tortoisesvn-diff-doc]. 78 | 79 | Additional Documentation 80 | ------------------------ 81 | - [License (MIT)][docs.license] 82 | - [Changelog][docs.changelog] 83 | 84 | [andivionian-status-classifier]: https://github.com/ForNeVeR/andivionian-status-classifier#status-aquana- 85 | [docs.changelog]: CHANGELOG.md 86 | [docs.license]: License.md 87 | [docs.ui-form]: docs/ui-screenshot.png 88 | [status-aquana]: https://img.shields.io/badge/status-aquana-yellowgreen.svg 89 | [tortoisesvn-diff-doc]: https://sourceforge.net/p/tortoisesvn/code/27268/tree/trunk/contrib/diff-scripts/diff-doc.js 90 | -------------------------------------------------------------------------------- /Gui-Diff-Word.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | Add-Type -AssemblyName System.Windows.Forms 3 | 4 | $Form = New-Object System.Windows.Forms.Form 5 | $Form.ClientSize = New-Object System.Drawing.Point(595, 162) 6 | $Form.text = 'Compare MS Word Documents (drop files here)' 7 | #Make a form topmost window - good for drag and drop operations 8 | $Form.TopMost = $true 9 | $Form.FormBorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle 10 | 11 | $olddoc = New-Object System.Windows.Forms.TextBox 12 | $olddoc.multiline = $true 13 | $olddoc.width = 580 14 | $olddoc.height = 50 15 | $olddoc.location = New-Object System.Drawing.Point(6, 8) 16 | $olddoc.AllowDrop = $true 17 | 18 | $newdoc = New-Object System.Windows.Forms.TextBox 19 | $newdoc.multiline = $true 20 | $newdoc.width = 580 21 | $newdoc.height = 50 22 | $newdoc.location = New-Object System.Drawing.Point(6, 65) 23 | $newdoc.AllowDrop = $true 24 | 25 | $cmpbtn = New-Object System.Windows.Forms.Button 26 | $cmpbtn.text = "Compare" 27 | $cmpbtn.width = 446 28 | $cmpbtn.height = 30 29 | $cmpbtn.location = New-Object System.Drawing.Point(140, 126) 30 | 31 | $clrbtn = New-Object System.Windows.Forms.Button 32 | $clrbtn.text = "Clear" 33 | $clrbtn.width = 120 34 | $clrbtn.height = 30 35 | $clrbtn.location = New-Object System.Drawing.Point(6, 126) 36 | 37 | $Form.controls.AddRange(@($olddoc, $newdoc, $cmpbtn, $clrbtn)) 38 | 39 | $olddoc.Add_DragDrop( { DnD $this $_ }) 40 | $olddoc.Add_DragOver( { DnO $this $_ }) 41 | $newdoc.Add_DragDrop( { DnD $this $_ }) 42 | $newdoc.Add_DragOver( { DnO $this $_ }) 43 | $cmpbtn.Add_Click( { DoCompare $this $_ }) 44 | $clrbtn.Add_Click( { ClearInputs $this $_ }) 45 | 46 | function DnO ($evSource, $evtArgs) { 47 | if ($evtArgs.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) { 48 | $evtArgs.Effect = [Windows.Forms.DragDropEffects]::Copy 49 | } 50 | else { 51 | $evtArgs.Effect = [Windows.Forms.DragDropEffects]::None 52 | } 53 | $Form.ActiveControl = $cmpbtn 54 | } 55 | function DnD ($evSource, $evtArgs) { 56 | 57 | $files = $evtArgs.Data.GetData([Windows.Forms.DataFormats]::FileDrop) 58 | if ($files.Count -eq 1) 59 | { 60 | $evSource.text = $files[0] 61 | } 62 | elseif ($files.Count -gt 1) 63 | { 64 | if ($evSource -eq $olddoc) 65 | { 66 | $olddoc.text = $files[0] 67 | $newdoc.text = $files[1] 68 | } 69 | else 70 | { 71 | $olddoc.text = $files[1] 72 | $newdoc.text = $files[0] 73 | } 74 | } 75 | } 76 | function DoCompare ($evSource, $evArgs) { 77 | $Form.TopMost = $false 78 | $BaseFileName = $olddoc.text 79 | $ChangedFileName = $newdoc.text 80 | # Remove the readonly attribute because Word is unable to compare readonly 81 | # files: 82 | $baseFile = Get-ChildItem $BaseFileName 83 | if ($baseFile.IsReadOnly) { 84 | $baseFile.IsReadOnly = $false 85 | } 86 | # Constants 87 | $wdDoNotSaveChanges = 0 88 | $wdCompareTargetNew = 2 89 | try { 90 | $word = New-Object -ComObject Word.Application 91 | $word.Visible = $true 92 | $document = $word.Documents.Open($BaseFileName, $false, $false) 93 | $document.Compare($ChangedFileName, [ref]"Comparison", [ref]$wdCompareTargetNew, [ref]$true, [ref]$true) 94 | 95 | $word.ActiveDocument.Saved = 1 96 | 97 | # Now close the document so only compare results window persists: 98 | $document.Close([ref]$wdDoNotSaveChanges) 99 | } 100 | catch { 101 | [System.Windows.Forms.MessageBox]::Show($_.Exception) 102 | } 103 | } 104 | function ClearInputs ($evSource, $evtArgs) { 105 | $olddoc.text = "" 106 | $newdoc.text = "" 107 | $Form.TopMost = $true 108 | } 109 | 110 | [void]$Form.ShowDialog() 111 | --------------------------------------------------------------------------------