Local html to blogger

Local html to blogger
# HelloBlogger Local HTML

This example shows how to create a new Blogger post from a local HTML file by using PowerShell.

## What The Script Does

The script in this folder:

- reads a local HTML file
- extracts the first `<title>...</title>` tag
- uses that title as the Blogger post title
- sends the full HTML file content as the Blogger post body
- opens a browser-based Google consent flow at runtime and exchanges the returned authorization code for an OAuth access token
- stores the access token and refresh token in a local cache file encrypted with a password variable so future runs can reuse them when they are still valid
- creates Blogger drafts by default instead of publishing them immediately

## Important Constraint

Blogger does not allow creating posts with only an API key. An API key identifies your Google Cloud project, but Blogger write operations also require an OAuth access token for a Google account that has permission to publish to the target blog.

That means the script requires all of the following values:

- `ApiKey` - included in the Blogger API request URL
- `BlogId` - the Blogger blog ID to publish into
- `ClientId` - OAuth client ID used during the runtime consent flow
- `ClientSecret` - OAuth client secret used during the runtime consent flow
- `HtmlFilePath` - path to the local HTML file that will become the blog post content
- `CreateAsDraft` - fixed script variable that keeps the Blogger post in draft state

These values are set directly near the top of `publish-blogger-post.ps1`. The script does not accept command-line arguments or interactive prompts.

## Files

- `publish-blogger-post.ps1` - PowerShell script that publishes the HTML file as a new Blogger post
- `build-exe.ps1` - build helper that compiles `publish-blogger-post.ps1` into an EXE
- `sample-post.html` - example HTML report with a `<title>` tag set to `Drafts in blogger`
- `tools/modules/ps2exe` - project-local `ps2exe` module used by the build script

## Requirements

- Windows PowerShell 5.1 or PowerShell 7+
- a Google Cloud project with the Blogger API enabled
- a Blogger blog you can publish to
- OAuth client credentials for a Google account that has author or admin access to that blog

## Folder Layout

```text
helloblogger-localhtml/
|-- build-exe.ps1
|-- publish-blogger-post.ps1
|-- sample-post.html
|-- tools/
|   `-- modules/
|       `-- ps2exe/
`-- README.md
```

## Step 1: Enable Blogger API

On the machine where you want to run this:

1. Open Google Cloud Console.
2. Select or create a project.
3. Enable the Blogger API for that project.
4. Create an API key.
5. Create OAuth credentials for a desktop app.

The script uses the API key in the request URL and opens a Google sign-in flow at runtime. On the first run, it captures the authorization code on `localhost`, exchanges it for access and refresh tokens, and saves them to the local cache file.

The script also writes a local cache file named `.blogger-oauth-cache.json` in this folder. The file content is encrypted with the `TokenCachePassword` value that you set near the top of the script. On later runs it will:

1. Reuse a still-valid cached access token if one exists.
2. Otherwise, use a cached refresh token to mint a new access token.
3. Only open the browser if neither cached token can be used or the cache is missing.

## Step 2: Get Your Blogger Blog ID

Use the blog ID for the target blog you want to publish to. If you do not already know it, you can usually find it in Blogger settings or retrieve it from Blogger API tooling tied to your account.

## Step 3: Prepare Your HTML File

Your HTML file must contain a `<title>` tag. The script uses that value as the title of the new Blogger post.

The included `sample-post.html` is a plain HTML report with no styles. It documents the steps used to inspect the publisher and create the draft post, and its title is set to `Drafts in blogger`.

Example:

```html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Hello From Local HTML</title>
</head>
<body>
	<h1>Hello From Local HTML</h1>
	<p>This content becomes the Blogger post body.</p>
</body>
</html>
```

## Step 4: Run The Script Directly (PS1)

Edit the variable values near the top of `publish-blogger-post.ps1`, then open PowerShell in this folder and run:

```powershell
./publish-blogger-post.ps1
```

On the first run, the script opens a browser for Google sign-in, captures the authorization code on `localhost`, and stores the resulting tokens in the local cache file. Later runs reuse the cached access token or refresh token when they are still valid. The Blogger post is created as a draft because `CreateAsDraft` is set to `$true` in the script.

If you want to preview the extracted title and generated JSON body without sending a network request, set `$Preview = $true` near the top of the script before running it.

## Step 5: Build An EXE

This folder includes a helper script to compile the PowerShell script into an executable:

```powershell
./build-exe.ps1
```

Optional arguments:

- `-OutputPath` to choose where the EXE is written (default: `./publish-blogger-post.exe`)
- `-NoConsole` to build a no-console executable

Example:

```powershell
./build-exe.ps1 -OutputPath ./dist/publish-blogger-post.exe
```

The build script uses the project-local `ps2exe` module under `tools/modules/ps2exe`, so it does not require `Install-Module` on the target machine.

## Step 6: Run The Generated EXE

After build completion, run:

```powershell
./publish-blogger-post.exe
```

Preview mode is also available if you set `$Preview = $true` before building the EXE.

## Parameters

### `ApiKey`

Google Cloud API key for the project that has Blogger API enabled.

### `BlogId`

The Blogger blog ID where the post should be created.

### `ClientId`

OAuth client ID used in the runtime Google consent flow.

### `ClientSecret`

OAuth client secret used in the runtime Google consent flow.

### `RedirectUri`

Local loopback redirect URI used to capture the Google authorization code. The default is `http://localhost:8765/`.

### `HtmlFilePath`

Path to the local HTML file to publish.

### `Preview`

Set this to `$true` to preview the resolved file path, extracted title, and generated JSON payload without calling Blogger.

### `TokenCachePassword`

Password used to encrypt and decrypt the local OAuth token cache file.

### `.blogger-oauth-cache.json`

Local cache file that stores the most recent OAuth tokens in encrypted form. The script manages this file automatically.

## Expected Publish Result

When the Blogger API call succeeds, the script outputs a PowerShell object with:

- `Id`
- `Title`
- `Url`
- `Published`
- `IsDraft`

## Common Errors

### Missing `<title>` tag

If the HTML file does not contain a `<title>` tag, the script stops with an error.

### Unauthorized request

If consent is canceled, the browser callback fails, or the Google account does not have publish rights, Blogger will reject the runtime token request.

### Cached token expired

If the cached access token has expired, the script uses the cached refresh token to mint a new one. If both cached values are unusable, it starts the browser consent flow again.

### API key only

If you try to create a draft with only an API key and no valid OAuth bearer token, the request will fail. Blogger draft creation is an authenticated write operation.

## Example For Another Machine

Copy this folder to the target machine, then:

1. Install PowerShell if it is not already available.
2. Enable Blogger API in Google Cloud.
3. Obtain an API key.
4. Create OAuth desktop credentials for the Blogger account.
5. Update or replace `sample-post.html` with your own HTML file.
6. Build EXE by running `./build-exe.ps1`.
7. Run `./publish-blogger-post.exe`.

## End-To-End Quick Commands

```powershell
# Build EXE
./build-exe.ps1

# Preview payload via EXE
# Set $Preview = $true before building the EXE

# Publish via EXE
./publish-blogger-post.exe
```

## Commands

```
.\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1
.\reference-stack-8\helloblogger-localhtml\build-exe.ps1
.\reference-stack-8\helloblogger-localhtml\publish-blogger-post.exe
```

## Errors Fixed

```
.\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1

.\reference-stack-8\helloblogger-localhtml\build-exe.ps1
.\reference-stack-8\helloblogger-localhtml\publish-blogger-post.exe


PS C:\Users\c\Documents\c> .\reference-stack-8\helloblogger-localhtml\publish-blogger-post.exe
ERROR: Cannot bind argument to parameter 'Path' because it is an empty string.

PS C:\Users\c\Documents\c> .\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1
Opening browser for Google sign-in...
Get-OAuthAccessToken : The term 'if' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.
At C:\Users\c\Documents\c\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1:368 char:20
+ ... cessToken = Get-OAuthAccessToken -RuntimeClientId $ClientId -RuntimeC ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (if:String) [Get-OAuthAccessToken], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException,Get-OAuthAccessToken

PS C:\Users\c\Documents\c> .\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1
Opening browser for Google sign-in...
ConvertTo-EncryptedPayload : Method invocation failed because [System.Security.Cryptography.RandomNumberGenerator] does not contain a method named 'Fill'.
At C:\Users\c\Documents\c\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1:137 char:25
+ ... edPayload = ConvertTo-EncryptedPayload -PlainText ($cacheObject | Con ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  + CategoryInfo          : InvalidOperation: (:) [ConvertTo-EncryptedPayload], RuntimeException
  + FullyQualifiedErrorId : MethodNotFound,ConvertTo-EncryptedPayload


Opening browser for Google sign-in...
HTML file not found: .\sample-post.html
At C:\Users\c\Documents\c\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1:423 char:5
+     throw "HTML file not found: $HtmlFilePath"
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  + CategoryInfo          : OperationStopped: (HTML file not found: .\sample-post.html:String) [], RuntimeException
  + FullyQualifiedErrorId : HTML file not found: .\sample-post.html


PS C:\Users\c\Documents\c> .\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1
Using cached access token from C:\Users\c\Documents\c\reference-stack-8\helloblogger-localhtml\.blogger-oauth-cache.json
Invoke-RestMethod : {
"error": {
"code": 400,
"message": "Invalid value at 'post' (content), Starting an object on a scalar field",
"errors": [
{
"message": "Invalid value at 'post' (content), Starting an object on a scalar field",
"reason": "invalid"
}
],
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "post",
"description": "Invalid value at 'post' (content), Starting an object on a scalar field"
}
]
}
]
}
}
At C:\Users\c\Documents\c\reference-stack-8\helloblogger-localhtml\publish-blogger-post.ps1:428 char:13
+ $response = Invoke-RestMethod -Method Post -Uri $requestUri -Headers  ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
  + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

```