FreeSoftwareServers
FreeSoftwareServers

Reputation: 2821

Check if Word Document is Read Only from PowerShell

I'm trying to detect if a "Microsoft Word Document" has been configured "Read-Only" by MSOffice.

Everything I have read relates to checking using Get-ItemProperty "C:\tmp\readonly.docx" | Select-Object IsReadOnly, but that is checking if the File is "read only" from the filesystem level.

The Problem is Microsoft doesn't mark it on the outside, you'd need to open/check with the Microsoft COM object I figure to query if document is read only.

PS C:\Users\Admin> Get-ItemProperty "C:\tmp\readonly.docx" | Select-Object IsReadOnly


IsReadOnly
----------
     False

Update: If file is configured RO without a Password then you can simple open as RW without a prompt (via powershell), but if it is with a Password then you get the prompt to acknowledge RO status which is what I want to avoid because it's hanging my script.

Upvotes: 2

Views: 1390

Answers (2)

postanote
postanote

Reputation: 16116

Continuing from my comment and note, not using anything dealing with the Word DOM via COM.

$File = 'd:\Temp\HSGCopy.docx'

# File not in use
Set-ItemProperty -Path $File  -Name IsReadOnly -Value $false
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File | 
ForEach{
    try   
    {
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                      )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf  
    }
    catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
    catch {$PSItem.Exception.Message}
}
# Results
<#
False
What if: Performing the operation "Remove File" on target "D:\Temp\HSGCopy.docx".
#>

# File in use
Set-ItemProperty -Path $File  -Name IsReadOnly -Value $false
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File | 
ForEach{
    try   
    {
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                      )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf  
    }
    catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
    catch {$PSItem.Exception.Message}
}
# Results
<#
False
Exception calling "Open" with "3" argument(s): "The process cannot access the file 'd:\Temp\HSGCopy.docx' because it is being used by another process."
#>


# Change the file attribute
# File not in use
Set-ItemProperty -Path $File  -Name IsReadOnly -Value $true
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File | 
ForEach{
    try   
    {
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                      )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf  
    }
    catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
    catch {$PSItem.Exception.Message}
}
# Results
<#
True
Exception calling "Open" with "3" argument(s): "Access to the path 'd:\Temp\HSGCopy.docx' is denied."
#>

# File in use
Set-ItemProperty -Path $File  -Name IsReadOnly -Value $true
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File | 
ForEach{
    try   
    {
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                      )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf  
    }
    catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
    catch {$PSItem.Exception.Message}
}
# Results
<#
True
Exception calling "Open" with "3" argument(s): "Access to the path 'd:\Temp\HSGCopy.docx' is denied."
#>

When using Word document protection

# with Word doc protection off
#>
$Word = New-Object –comobject Word.Application
Try
{
    ($Word.documents.open($File,$false,$false)).ReadOnly
    Write-Warning -Message "$File is protected ReadOnly"
}
Catch {Write-Verbose -Message "$File is not protected" -Verbose}

# then don't forget to close
$Word.Quit()
# Results 
<#
VERBOSE: d:\Temp\HSGCopy.docx is not protected
#>




# With Word doc protection on
$Word = New-Object –comobject Word.Application
Try
{
    ($Word.documents.open($File,$false,$false)).ReadOnly
    Write-Warning -Message "$File is protected ReadOnly"
}
Catch {Write-Verbose -Message "$File is not protected ReadOnly" -Verbose}

# then don't forget to close
$Word.Quit()
# Results 
<#
True
WARNING: d:\Temp\HSGCopy.docx is protected ReadOnly
#>

By accident or on purpose, you could have both set in an environment. I've had this happen to me in auto-classification scenarios. Meaning when FSRM/RMS/AIP has been deployed/implemented and enforced.

Update

Here a sample of what I have in my workflow to catch this sort of stuff, as per our exchange.

Clear-Host
$Files | 
ForEach{
    $File = $PSItem
    "Processing $PSItem"
    try   
    {
        Write-Verbose -Message 'Word properties:
        DocID, FullName, HasPassword, 
        Permission, ReadOnly, Saved, 
        Creator, CurrentRsid, CompatibilityMode' -Verbose

        'DocID', 'FullName', 'HasPassword', 
        'Permission', 'ReadOnly', 'Saved', 
        'Creator', 'CurrentRsid', 'CompatibilityMode' | 
        ForEach {($Word.documents.open($File,$false,$false)).$PSitem}

        Write-Verbose -Message 'File system ReadOnly attribute:' -Verbose
        (Get-ItemProperty $File).IsReadOnly

        Write-Verbose -Message 'Document state' -Verbose
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                        )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf  
    }
    catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
    catch {$PSItem.Exception.Message}
}
# Results
<#
Processing d:\Temp\HSGCopy.docx
VERBOSE: Word properties:
        DocID, FullName, HasPassword, 
        Permission, ReadOnly, Saved, 
        Creator, CurrentRsid, CompatibilityMode
938207550
D:\Temp\HSGCopy.docx
False
True
True
1297307460
12414886
15
VERBOSE: File system ReadOnly attribute:
False
VERBOSE: Document state
What if: Performing the operation "Remove File" on target "D:\Temp\HSGCopy.docx".
#>

Upvotes: 1

FreeSoftwareServers
FreeSoftwareServers

Reputation: 2821

Ok, so to test this, I had 3 files as follows:

  • A) One regular test.docx with no restrictions
  • B) One readonly.docx w/ a password. This is the file that hung me up w/ a prompt
  • C) One nopass.docx w/ a read-only setting, but no PWD configured.

A and C would open regardless of the ReadOnly setting, but B would hang on a prompt even with DisplayAlerts set to 0. You also couldn't check the ReadOnly property unless the prompt was surpassed, or if you set it to TRUE it was always true obviosuly.

There is no way I found to check the ReadOnly or HasPassword property without first openeing document. You can likely inspect the XML file for HEX but I'd say that is less reliable. My way just took some testing/trickery to get working. The important part was I had to pass a password and catch if it failed. Doc's A/C would open fine even when you pass a password argument, so no harm there. In the catch I set ReadOnly = TRUE and Visible = TRUE. You may not need to set the visible part true, but if ReadOnly = TRUE then you can't make certain adjustments via VB (like ORIENTATION) and I'll be using SENDKEYS so I'll need the UI if ReadOnly = TRUE. As well, hiding the UI is just a "bonus" but not needed. I may just set it always visible if I continue wasting time on coding IF/THEN for the OPENUI statments.

Anyway... Here is a final code snippet to test on the three files which should result in each file opening w/o a prompt.

 #Constants
 Clear-Variable ReadOnly
 $missing = [System.Type]::Missing
 $str = ''
 $PASSWD = 'IsPWDProtected?'
 $wdAlertsNone = 0
 $FILENAME = "C:\tmp\readonly.docx"
 $OPENUI = "TRUE"

 #Start Word
 $ObjWord = New-Object -comobject Word.Application
 IF ($OPENUI -eq "FALSE"){$ObjWord.Visible  = $FALSE}ELSE{$ObjWord.Visible  = $TRUE}
 $ObjWord.Application.DisplayAlerts = $wdAlertsNone

 #.Open
 IF (!$ConfirmConversions){$ConfirmConversions = $missing}
 IF (!$ReadOnly){$ReadOnly = $FALSE}
 IF (!$AddToRecentFiles){$AddToRecentFiles = $missing}
 IF (!$PasswordDocument){$PasswordDocument = $PASSWD}
 IF (!$PasswordTemplate){$PasswordTemplate = $PASSWD}
 IF (!$Revert){$Revert = $False} 
 IF (!$WritePasswordDocument){$WritePasswordDocument =  $PASSWD}
 IF (!$WritePasswordTemplate){$WritePasswordTemplate =  $PASSWD} 
 IF (!$Format){$Format = 'wdOpenFormatAuto'} 
 IF (!$Encoding){$Encoding = $missing}
 IF (!$Visible){$Visible = $False} 
 try{$ObjDoc=$ObjWord.documents.open($FILENAME,$ConfirmConversions,$ReadOnly,$AddToRecentFiles,$PasswordDocument,$PasswordTemplate,$Revert,$WritePasswordDocument,$WritePasswordTemplate,$Format,$Encoding,$Visible)}
  catch {
     Write-Error  $_
     Write-Host "Opening Read_Only"
     $ReadOnly = $TRUE
     $Visible = $TRUE
     $ObjDoc=$ObjWord.documents.open($FILENAME,$ConfirmConversions,$ReadOnly,$AddToRecentFiles,$PasswordDocument,$PasswordTemplate,$Revert,$WritePasswordDocument,$WritePasswordTemplate,$Format,$Encoding,$Visible)
 }

#AllDone?
PAUSE
$ObjWord.ActiveDocument.Close(0)
$ObjWord.Quit()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
sleep 2

Result:

PS C:\Users\Admin> C:\tmp\test.ps1
C:\tmp\test.ps1 : The password is incorrect. Word cannot open the document. (C:\tmp\readonly.doc)
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,test.ps1
 
Opening Read_Only
Press Enter to continue...: 

Note: I hardcoded OPENUI = TRUE during testing because if it got hung on a prompt with the UI closed, I had to use tskill winword.exe and start over.

Upvotes: 0

Related Questions