├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── Readme.md
├── demo-staging
├── backend.tf
├── clearwaiters.sh
├── copyBootstrapArtifacts.sh
├── getDomainPassword.sh
├── main.tf
├── outputs.tf
├── policy.json
└── prepareProject.sh
├── docs
├── ClickToDeploy.md
├── kms.md
└── runtime-config.md
├── modules
├── SQLServerWithStackdriver
│ ├── main.tf
│ ├── vars.tf
│ └── versions.tf
├── network
│ ├── main.tf
│ ├── outputs.tf
│ └── vars.tf
├── windowsDCWithStackdriver
│ ├── main.tf
│ ├── outputs.tf
│ ├── vars.tf
│ └── versions.tf
├── windowsWithStackdriver
│ ├── main.tf
│ ├── vars.tf
│ └── versions.tf
└── wsus
│ ├── main.tf
│ └── vars.tf
└── powershell
├── bootstrap
├── domain-member.ps1
├── initial_alwayson_startup_script.ps1
├── install-sql-server-principal-step-1.ps1
├── primary-domain-controller-step-1.ps1
├── primary-domain-controller-step-2.ps1
└── sql_install.ps1
├── c2d
├── c2d_base.psm1
├── gce_base.psm1
├── initial_win_startup_script.ps1
└── sql_install.ps1
├── comprehensive-runtime-config.ps1
└── templates
└── windows-stackdriver-setup.ps1
/.gitignore:
--------------------------------------------------------------------------------
1 | .terraform/
2 | ./*.tfstate
3 | *.log
4 | *.bak
5 | .VSCodeCounter/
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows
28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Automated Terraform AlwaysOn deployment
2 |
3 | ## What is the Goal?
4 |
5 | This will deploy a Windows environment on to Google Cloud Platform (GCP). This will include Windows Servers with SQL Server installed with AlwaysOn AG.
6 |
7 | ## Time Considerations
8 |
9 | The creation of the infrastructure is just the start, which takes about 4 minutes.
10 |
11 | It then takes about 15 minutes for the domain controller to get configured. The SQL Server VMs will wait for this to happen. After the domain controller has completed the SQL Server VMs will take an approximately 10 more minutes.
12 |
13 | ## Permissions
14 |
15 | Before you start running Terraform, you need to create a service account in your project for Terraform to run as. Under IAM and Admin --> Service accounts, create a serice account and then download the key.
16 | Give the service account the permissions it needs to create infrastructure in your project.
17 |
18 | Upload the key to wherever you are running the terraform and set GOOGLE_APPLICATION_CREDENTIALS to use this key ( or follow adding credntials in https://www.terraform.io/docs/providers/google/getting_started.html##)
19 |
20 |
21 | ## Dependencies
22 |
23 | On a machine with `terraform` (at least 0.13) and `git` (the Google Cloud Shell can be leveraged as well):
24 |
25 | ```sh
26 | gcloud source repos clone terraform-sqlserver-alwayson --project=cloud-ce-shared-code
27 | ```
28 |
29 | Or:
30 |
31 | ```sh
32 | git clone https://github.com/GoogleCloudPlatform/terraform-sqlserver-alwayson.git
33 | ```
34 |
35 | Let's get into that directory:
36 |
37 | ```sh
38 | cd terraform-sqlserver-alwayson/demo-staging/
39 | ```
40 |
41 | Here we'll update the deployment variables in `prepareProject.sh`. Edit everything within the single quotes in `prepareProject.sh` (Note: `projectNumber` doesn't need single quotes, just the number itself):
42 |
43 | ```
44 | region='{your-region-here}'
45 | zone='{your-zone-here}'
46 |
47 | project='{your-project-id}'
48 | projectNumber={your-project-number}
49 |
50 | #differentiate this deployment from others. Use lowercase alphanumerics between 6 and 30 characters.
51 | prefix='{desired-domain-name-and-unique-seed-for-bucket-name}'
52 |
53 | #user you will be running as
54 | user='{user-you-will-run-as}'
55 | ```
56 |
57 | :bangbang: For deployment troubleshooting, try entering in another unique `prefix`.
58 |
59 | Now we'll update the terraform project in the environment folder containing `main.tf` and `backend.tf`, by running:
60 |
61 | ```sh
62 | ./prepareProject.sh
63 | ```
64 |
65 | Here's what's happening:
66 |
67 | * In backend.tf
68 | * bucket = "{common-backend-bucket}": change this to the bucket in your project where you will store the state
69 | * project = "{cloud-project-id}" : change this to the id of your project
70 | * In main.tf of the environment (also done by prepareProject.sh)
71 | * project = "{cloud-project-id}"
72 | * region = "{cloud-project-region}"
73 | * primaryzone = "{cloud-project-zone}"
74 | * gcs-prefix = "gs://{common-backend-bucket}"
75 | * keyring = "{deployment-name}-deployment-ring"
76 | * kms-key = "{deployment-name}-deployment-key"
77 | * domain = "{deployment-name}.com"
78 | * dc-netbios-name = "{deployment-name}"
79 | * runtime-config = "{deployment-name}-runtime-config"
80 | * Update the gcs-prefix (done in prepareProject.sh)
81 | * In GCP
82 | * Enable APIs
83 | * KMS - gcloud services enable cloudkms.googleapis.com
84 | * Runtime configurator - gcloud services enable runtimeconfig.googleapis.com
85 | * cloud resource manager
86 | * compute manager
87 | * iam
88 | * Make a bucket for:
89 | * state file
90 | * passwords
91 | * powershell scripts
92 | * copy up the required bootstrap scripts
93 | * create a new admin service account
94 | * bind the logged on user to that service account (can ran as this service account)
95 | * make the service account a project editor
96 | * create key ring
97 | * create a key
98 | * give the new admin user and the project service account rights to encrypt/decrypt with the kms key
99 | ```bash
100 |
101 | gcloud services enable cloudkms.googleapis.com
102 | gcloud services enable runtimeconfig.googleapis.com
103 | gcloud services enable cloudresourcemanager.googleapis.com
104 | gcloud services enable compute.googleapis.com
105 | gcloud services enable iam.googleapis.com
106 |
107 | #create the bucket
108 | gsutil mb -p $project gs://$bucketName
109 | gsutil -m cp -r ../powershell/bootstrap gs://$bucketName/powershell/bootstrap/
110 |
111 | gcloud kms keyrings create acme-deployment-ring --location=us-central1
112 | gcloud kms keys create acme-deployment-key --location=us-central1 --keyring=myring --purpose=encryption
113 |
114 | ```
115 |
116 | ## Terraforming the Environment
117 |
118 | Next we'll run:
119 |
120 | ```sh
121 | terraform init
122 | ```
123 | Followed by
124 |
125 | ```sh
126 | terraform apply
127 | ```
128 | You might encounter some warnings about interpolation-only expressions, due to changes between TF versions but they can safely be ignored. as of 0.13.2
129 |
130 | ## Windows Background
131 | NetBIOS is a legacy network application used by windows for active directory. It limits the names of machines to 15 characters. For this reason we must observe this limit on our computer names for our deployment to succeed.
132 |
133 | ### Naming Convention
134 | We are limited as described above.
135 | ${var.deployment-name} - a unique 8 character deployment name
136 | ${var.function} - 3 characters decribing the purpose of the instance
137 | ${var.instancenumber} - two digits
138 | computername = "${var.deployment-name}-${var.function}-${var.instancenumber}"
139 |
140 | ## For Debugging purposes:
141 | domain admin: usr: {full domain name}\Administrator pw:
142 | * Domain Controller Password:
143 | * SQL 1 Password:
144 | * SQL 2 Password:
145 | * SQL 3 Password:
146 |
147 | ## Project Layout
148 | There are folders for environment-specific content such as sandbox, clickToDeploy and acme-staging. Modules, used by the deployment scripts, can be found in the ./modules directory. The contents of the docs directory is for documentary purposes, even if it is code. The 2 shell scripts in the environment folders are:
149 | * clearwaiters.sh - if you are redeploying only the sql servers (you havent destroyed the whole environment including the runtime-config) this script will delete the waiters.
150 | * copyBootstrapArticles.sh - will copy essential scripts from ../powershell/bootstrap/ to {your deployment bucket (gcs-prefix in main.tf)}/powershell/bootstrap/
151 |
152 | ## Runtime-Config nuances
153 | Runtime-config has limited support in terraform. In deployment manager one can create the config and variables. In terraform, you can only create the runtime config, variable and waiters must be created in powershell scipts or using command line or rest API.
154 |
155 | The following deletes a waiter, which you might need to do if you redeploy
156 |
157 | ``` bash
158 | gcloud beta runtime-config configs waiters delete clicktodeploy-dev-sql-p-01_waiter --config-name=acme-runtime-config
159 | ```
160 |
161 | ## TO connect to the instances we need firewall rules allowing access
162 | The network module has a defult firewall resource that allows access for 3389 and 8080 to machines tagged we, pdc pr sql. If you are testing in a google project, your rules will be deleted by gce enforcer every 15 minutes and you will need to recreate your rule.
163 |
164 | 1. Go to www.whatismyip.com and find your external ip address
165 | 2. Ensure your ip address with /32 (only that ip address) is in the source range
166 | 3. Ensure your the target tags list contains the tag of the machine you are trying to get to.
167 |
168 | ``` bash
169 | terraform apply --target=module.create-network.google_compute_firewall.default
170 | ```
171 |
172 | ## ClickToDeploy
173 | ``` Powershell
174 |
175 | $script:gce_install_dir = 'C:\Program Files\Google\Compute Engine\sysprep'
176 |
177 | $Script:c2d_scripts_bucket = 'c2d-windows/scripts'
178 | $Script:install_path="C:\C2D" # Folder for downloads
179 | $script:show_msgs = $false
180 | $script:write_to_serial = $false
181 |
182 | # Instance specific variables
183 | $script_name = 'sql_install.ps1'
184 | $script_subpath = 'sqlserver'
185 | $task_name = "SQLInstall"
186 |
187 | # Download the scripts
188 | # Base Script
189 | $base_script_path = "$Script:c2d_scripts_bucket/c2d_base.psm1"
190 | $base_script = "$Script:install_path\c2d_base.psm1"
191 |
192 | # Run Script
193 | $run_script = "$Script:install_path\$script_name"
194 | $run_script_path = "$Script:c2d_scripts_bucket/$script_subpath/$script_name"
195 | ```
196 |
197 | So...
198 | ClickToDeploy depends upon (preinstalled on all windows instances):
199 | 1. C:\Program Files\Google\Compute Engine\sysprep\gce_base.psm1
200 |
201 | We are downloading:
202 | 1. From "gs://c2d-windows/scripts/c2d_base.psm1"
203 | 2. To "C:\C2D\c2d_base.psm1"
204 | 3. From: "gs://c2d-windows/scripts/sqlserver/sql_install.ps1"
205 | 4. To: "C:\C2D\sql_install.ps1"
206 |
207 | These are provided for reference purposes in powershell/c2d
208 |
209 | ### gce_base.ps1 - This provides a library of functions for interfacing with GCE (pre-installed)
210 | * Get-Metadata
211 | * Generate-Random_Password
212 | * Write-Serial-Port
213 | * Write-Log
214 |
215 | ### c2d_base.ps1 - Library of c2d flow control libraries
216 | * Write-Logger - Write log messages to instance log
217 | * Write-ToReg - Write to registry
218 | * Runtime config functions for creating configs, variables and waiters
219 | * Functions for creating and deleting scheduled tasks
220 |
221 | ### sql_install.ps1 - Everything required to configure alwayson
222 | * Create a Windows Server Failover Cluster (WSFC)
223 | * Create an availability group
224 | * Create a database
225 | * Backup and restore a database from powershell
226 | * Create shared folders
227 | * Join a domain
228 | * Setup an entire cluster
229 |
230 | sql_install.ps1 gets called without any arguments from a scheduled task. It does the following:
231 | * SetScriptVar - Setup all the variables that will be consumed later in the setup
232 | * Reads the service account from c2d-property-sa-account
233 | * Reads domain name (Fully qualified) from c2d-property-domain-dns-name
234 | * Gets the Netbios domain by splitting domain on '.'
235 | * Reads sa password from c2d-property-sa-password
236 | * Reads list of nodes in cluster from sql-nodes into all_nodes
237 | * sets static ip addresses to 10.x.1.4
238 | * sets listener ip addresses to 10.x.1.5
239 | * it is assume the gateway and DC will always be 10.0.0.100
240 | * keep list of remote nodes (nodes this isnt running on) in remote_nodes
241 | * SetIP
242 | * Set IP addresses in Script:static_ip array
243 | * Set gateway to 10.0.0.100 in $Script:static_listner_ip
244 | * Add firewall rules for SQL server (1433) and AlwaysOn (5022) at windows level
245 |
246 | On all sql instances:
247 | ``` Powershell
248 | Install-WindowsFeature RSAT-AD-PowerShell
249 | Install-WindowsFeature Failover-Clustering -IncludeManagementTools
250 | ```
251 |
252 | ``` Powershell
253 | $Script:static_ip=@("10.10.0.3","10.10.0.4","10.10.0.5")
254 | $Script:cluster_name="cluster-dbclus"
255 | $Script:all_nodes_fqdn=@('c2d-sql-01.corp.acme.com','c2d-sql-02.corp.acme.com','c2d-sql-03.corp.acme.com')
256 | New-Cluster -Name $Script:cluster_name -Node $Script:all_nodes_fqdn -NoStorage -StaticAddress $Script:static_ip
257 |
258 | ```
259 |
260 | #The following is how you add a cluster in powershell. This must be run as domain admin
261 |
262 | ```Powershell
263 | $Script:cluster_name='cluster-dbclust'
264 | #$Script:all_nodes_fqdn ="c2d-sql-01.acme.com,c2d-sql-02.acme.com,c2d-sql-03.acme.com"
265 | $Script:all_nodes_fqdn =@("c2d-sql-01.acme.com","c2d-sql-02.acme.com","c2d-sql-03.acme.com")
266 | $Script:static_ip= @("10.1.0.4","10.2.0.4","10.3.0.4")
267 |
268 |
269 | Write-Host "Setting up cluster $Script:cluster_name for nodes $Script:all_nodes_fqdn and ips $Script:static_ip"
270 | # Create the cluster
271 | try {
272 | $result = New-Cluster -Name $Script:cluster_name -Node $Script:all_nodes_fqdn `
273 | -NoStorage -StaticAddress $Script:static_ip
274 | Write-Host "Result for setup cluster: $result"
275 | return $true
276 | }
277 | catch {
278 | Write-Host "** Failed to setup cluster: $Script:cluster_name ** "
279 | Write-Host $_.Exception.GetType().FullName
280 | Write-Host "$_.Exception.Message"
281 | return $false
282 | }
283 |
284 |
285 | #New-Cluster -Name "cluster-dbclus" -Node "c2d-sql-01.acme.com,c2d-sql-02.acme.com,c2d-sql-03.acme.com" -NoStorage -StaticAddress "10.1.0.4,10.2.0.4,10.3.0.4"
286 | New-Cluster -Name "cluster-dbclus" -Node @("c2d-sql-01","c2d-sql-02","c2d-sql-03") -NoStorage -StaticAddress @("10.1.0.4","10.2.0.4","10.3.0.4")
287 |
288 | ```
289 |
290 |
291 | # Known issues
292 | * Once complete, sometimes the two replicas are not synchonized. I think this is dues to the faiure of the script executed in sql_install.ps1._DBPermission. THis is currently taking nodes as an array as a parameter but it needs to rather loop through because SUSER_ID() does not take an array as a parameter. Not a big issue though because this is just a demo db.
293 | * removing and radding the db on nodes 2 and 3 succeeds.
294 | * Once the deployment is complete, the scopes of the machines can be reset and also the access to the kms key shuld be adjusted to reflect the desired administrative priorities.
295 | * TODO: Hardcoded domain ip in sql_install.ps1 10.0.0.100 replace with fetch from metadata
296 | * TODO: the getMetaData functions and Rutime-Config functions are repeated in the gce_base.psm1, c2d_base.psm1 and also in some of the ps1 scripts. In general, if we are importing a library that contains a function, it should be used in that function rather than re-implemented locally. Refactor this code to ensure optimal definition and implimentation of common functions.
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
--------------------------------------------------------------------------------
/demo-staging/backend.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | terraform {
17 | backend "gcs" {
18 | bucket = "{common-backend-bucket}"
19 | prefix = "/states/terraform.tfstate"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo-staging/clearwaiters.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2019 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | gcloud beta runtime-config configs waiters delete c2d-sql-01_waiter --config-name={deployment-name}-runtime-config
18 | gcloud beta runtime-config configs waiters delete c2d-sql-02_waiter --config-name={deployment-name}-runtime-config
19 | gcloud beta runtime-config configs waiters delete c2d-sql-03_waiter --config-name={deployment-name}-runtime-config
20 |
--------------------------------------------------------------------------------
/demo-staging/copyBootstrapArtifacts.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2019 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | gsutil cp ../powershell/bootstrap/*.ps1 gs://{common-backend-bucket}/powershell/bootstrap/
19 |
20 |
--------------------------------------------------------------------------------
/demo-staging/getDomainPassword.sh:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | gsutil cp gs://{common-backend-bucket}/output/domain-admin-password.bin .
17 | gcloud kms decrypt --key {deployment-name}-deployment-key --location {cloud-project-region} --keyring {deployment-name}-deployment-ring --ciphertext-file domain-admin-password.bin --plaintext-file domain-admin-password.txt
18 | cat domain-admin-password.txt
19 | rm domain-admin-password.txt
20 |
--------------------------------------------------------------------------------
/demo-staging/main.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | data "google_project" "project" {}
18 | // Configure the Google Cloud provider
19 | provider "google" {
20 | project = "{cloud-project-id}"
21 | region = "{cloud-project-region}"
22 | }
23 |
24 | locals {
25 | primaryzone = "{cloud-project-zone}"
26 | region = "{cloud-project-region}"
27 | hazone = "{cloud-project-hazone}"
28 | drregion = "{cloud-project-drregion}"
29 | drzone = "{cloud-project-drzone}"
30 | deployment-name = "{deployment-name}"
31 | environment = "dev"
32 | osimagelinux = "projects/eip-images/global/images/rhel-7-drawfork-v20180327"
33 | osimageWindows = "windows-server-2016-dc-v20181009"
34 | osimageSQL = "projects/windows-sql-cloud/global/images/sql-2017-enterprise-windows-2016-dc-v20181009"
35 | gcs-prefix = "gs://{common-backend-bucket}"
36 | keyring = "{deployment-name}-deployment-ring"
37 | kms-key = "{deployment-name}-deployment-key"
38 | primary-cidr = "10.0.0.0/16"
39 | second-cidr = "10.1.0.0/16"
40 | second-cidr-alwayson = "10.1.0.5/32"
41 | second-cidr-wsfc = "10.1.0.4/32"
42 | third-cidr = "10.2.0.0/16"
43 | third-cidr-alwayson = "10.2.0.5/32"
44 | third-cidr-wsfc = "10.2.0.4/32"
45 | fourth-cidr = "10.3.0.0/16"
46 | fourth-cidr-alwayson = "10.3.0.5/32"
47 | fourth-cidr-wsfc = "10.3.0.4/32"
48 | domain = "{windows-domain}.com"
49 | dc-netbios-name = "{windows-domain}"
50 | runtime-config = "{deployment-name}-runtime-config"
51 | all_nodes="{deployment-name}-sql-01|{deployment-name}-sql-02|{deployment-name}-sql-03"
52 | }
53 |
54 | module "create-network"{
55 | source = "../modules/network"
56 | network-name = "${local.deployment-name}-${local.environment}-net"
57 | primary-cidr = "${local.primary-cidr}"
58 | second-cidr = "${local.second-cidr}"
59 | third-cidr = "${local.third-cidr}"
60 | fourth-cidr = "${local.fourth-cidr}"
61 | primary-region = "${local.region}"
62 | dr-region = "${local.drregion}"
63 | deployment-name = "${local.deployment-name}"
64 | }
65 |
66 | //windows domain controller
67 | module "windows-domain-controller" {
68 | source = "../modules/windowsDCWithStackdriver"
69 | subnet-name = "${module.create-network.subnet-name}"
70 | secondary-subnet-name = "${module.create-network.subnet-name}"
71 | instancerole = "p"
72 | instancenumber = "01"
73 | function = "pdc"
74 | region = "${local.region}"
75 | keyring = "${local.keyring}"
76 | kms-key = "${local.kms-key}"
77 | kms-region ="${local.region}"
78 | environment = "${local.environment}"
79 | regionandzone = "${local.primaryzone}"
80 | osimage = "${local.osimageWindows}"
81 | gcs-prefix = "${local.gcs-prefix}"
82 | deployment-name = "${local.deployment-name}"
83 | domain-name = "${local.domain}"
84 | netbios-name = "${local.dc-netbios-name}"
85 | runtime-config = "${local.runtime-config}"
86 | wait-on = ""
87 | status-variable-path = "ad"
88 | network-tag = ["pdc"]
89 | network-ip = "10.0.0.100"
90 | }
91 |
92 | module "sql-server-alwayson-primary" {
93 | source = "../modules/SQLServerWithStackdriver"
94 | subnet-name = "${module.create-network.second-subnet-name}"
95 | alwayson-vip = "${local.second-cidr-alwayson}"
96 | wsfc-vip = "${local.second-cidr-wsfc}"
97 | instancerole = "p"
98 | instancenumber = "01"
99 | function = "sql"
100 | region = "${local.region}"
101 | keyring = "${local.keyring}"
102 | kms-key = "${local.kms-key}"
103 | kms-region="${local.region}"
104 | environment = "${local.environment}"
105 | regionandzone = "${local.primaryzone}"
106 | osimage = "${local.osimageSQL}"
107 | gcs-prefix = "${local.gcs-prefix}"
108 | deployment-name = "${local.deployment-name}"
109 | domain-name = "${local.domain}"
110 | netbios-name = "${local.dc-netbios-name}"
111 | runtime-config = "${local.runtime-config}"
112 | wait-on = "bootstrap/${local.deployment-name}/ad/success"
113 | domain-controller-address = "${module.windows-domain-controller.dc-address}"
114 | post-join-script-url = "${local.gcs-prefix}/powershell/bootstrap/install-sql-server-principal-step-1.ps1"
115 | status-variable-path = "mssql"
116 | network-tag = ["sql", "internal"]
117 | sql_nodes="${local.deployment-name}-sql-01|${local.deployment-name}-sql-02|${local.deployment-name}-sql-03"
118 |
119 | }
120 |
121 | module "sql-server-alwayson-secondary" {
122 | source = "../modules/SQLServerWithStackdriver"
123 | subnet-name = "${module.create-network.third-subnet-name}"
124 | instancerole = "s"
125 | instancenumber = "02"
126 | function = "sql"
127 | region = "${local.region}"
128 | keyring = "${local.keyring}"
129 | kms-key = "${local.kms-key}"
130 | kms-region="${local.region}"
131 | environment = "${local.environment}"
132 | regionandzone = "${local.hazone}"
133 | osimage = "${local.osimageSQL}"
134 | gcs-prefix = "${local.gcs-prefix}"
135 | deployment-name = "${local.deployment-name}"
136 | domain-name = "${local.domain}"
137 | netbios-name = "${local.dc-netbios-name}"
138 | runtime-config = "${local.runtime-config}"
139 | wait-on = "bootstrap/${local.deployment-name}/ad/success"
140 | domain-controller-address = "${module.windows-domain-controller.dc-address}"
141 | post-join-script-url = "${local.gcs-prefix}/powershell/bootstrap/install-sql-server-principal-step-1.ps1"
142 | status-variable-path = "mssql"
143 | network-tag = ["sql", "internal"]
144 | sql_nodes="${local.deployment-name}-sql-01|${local.deployment-name}-sql-02|${local.deployment-name}-sql-03"
145 | alwayson-vip = "${local.third-cidr-alwayson}"
146 | wsfc-vip = "${local.third-cidr-wsfc}"
147 | }
148 |
149 | module "sql-server-alwayson-secondary-2" {
150 | source = "../modules/SQLServerWithStackdriver"
151 | subnet-name = "${module.create-network.fourth-subnet-name}"
152 | instancerole = "s"
153 | instancenumber = "03"
154 | function = "sql"
155 | region = "${local.drregion}"
156 | keyring = "${local.keyring}"
157 | kms-key = "${local.kms-key}"
158 | kms-region="${local.region}"
159 | environment = "${local.environment}"
160 | regionandzone = "${local.drzone}"
161 | osimage = "${local.osimageSQL}"
162 | gcs-prefix = "${local.gcs-prefix}"
163 | deployment-name = "${local.deployment-name}"
164 | domain-name = "${local.domain}"
165 | netbios-name = "${local.dc-netbios-name}"
166 | runtime-config = "${local.runtime-config}"
167 | wait-on = "bootstrap/${local.deployment-name}/ad/success"
168 | domain-controller-address = "${module.windows-domain-controller.dc-address}"
169 | post-join-script-url = "${local.gcs-prefix}/powershell/bootstrap/install-sql-server-principal-step-1.ps1"
170 | status-variable-path = "mssql"
171 | network-tag = ["sql", "internal"]
172 | sql_nodes="${local.deployment-name}-sql-01|${local.deployment-name}-sql-02|${local.deployment-name}-sql-03"
173 | alwayson-vip = "${local.fourth-cidr-alwayson}"
174 | wsfc-vip = "${local.fourth-cidr-wsfc}"
175 | }
176 |
--------------------------------------------------------------------------------
/demo-staging/outputs.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | output domain-controller-address {
17 | value = "${module.windows-domain-controller.dc-address}"
18 | }
19 |
--------------------------------------------------------------------------------
/demo-staging/policy.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [{
3 | "role": "roles/cloudkms.cryptoKeyDecrypter",
4 | "members": ["serviceAccount:{SvcAccount}", "user:{Usr}"]
5 | }, {
6 | "role": "roles/cloudkms.cryptoKeyEncrypter",
7 | "members": ["serviceAccount:{SvcAccount}", "user:{Usr}"]
8 | }]
9 | }
10 |
--------------------------------------------------------------------------------
/demo-staging/prepareProject.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2019 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | #Enter your project details for setting up the project dependencies
19 | region='{your-region-here}'
20 | zone=$region"-a"
21 | hazone=$region"-b"
22 | drregion='{your-dr-region}' #eg. us-east1 (b,c and d are valid)
23 | drzone=$drregion"-b"
24 |
25 | #Generate a uniqueu prefix for the bucket
26 | uniq=$(head /dev/urandom | tr -dc a-z0-9 | head -c 13 ; echo '')
27 |
28 | project='{your-project-id}'
29 | projectNumber={your-project-number}
30 |
31 | #differentiate this deployment from others. Use lowercase alphanumerics up to 8 characters.
32 | prefix='{desired-unique-prefix-for-resources}'
33 |
34 | #domain name (this will have .com added to make it fully qualified)
35 | domainName='{domain}'
36 |
37 | #user you will be running as (a fully google or gmail email address)
38 | user='{user-you-will-run-as}'
39 |
40 | #######################################################################################
41 | ### For the purposes of this demo script, you dont need to fill in anything past here
42 | #######################################################################################
43 |
44 | #bucket where your terraform state file, passwords and outputs will be stored
45 | bucketName=$uniq'-deployment-staging'
46 |
47 | kmsKeyRing=$prefix"-deployment-ring"
48 | kmsKey=$prefix"-deployment-key"
49 |
50 | echo $prefix
51 | echo $bucketName
52 | echo $kmsKeyRing
53 | echo $kmsKey
54 |
55 | # The files we have to substitute in are:
56 | # backend.tf clearwaiters.sh copyBootstrapArtifacts.sh getDomainPassword.sh main.tf
57 | sed -i "s/{common-backend-bucket}/$bucketName/g;s/{windows-domain}/$domainName/g;s/{cloud-project-id}/$project/g;s/{cloud-project-region}/$region/g;s/{cloud-project-zone}/$zone/g;s/{cloud-project-hazone}/$hazone/g;s/{cloud-project-drregion}/$drregion/g;s/{cloud-project-drzone}/$drzone/g;s/{deployment-name}/$prefix/g" backend.tf main.tf clearwaiters.sh copyBootstrapArtifacts.sh getDomainPassword.sh
58 |
59 | #########################################
60 | #enable the services that we depend upon
61 | ##########################################
62 | for API in compute cloudkms deploymentmanager runtimeconfig cloudresourcemanager iam storage-api storage-component
63 | do
64 | gcloud services enable "$API.googleapis.com" --project $project
65 | done
66 |
67 | #create the bucket
68 | gsutil mb -p $project gs://$bucketName
69 | gsutil -m cp -r ../powershell/bootstrap/* gs://$bucketName/powershell/bootstrap/
70 |
71 | DefaultServiceAccount="$projectNumber-compute@developer.gserviceaccount.com"
72 | AdminServiceAccountName="admin-$prefix"
73 | echo AdminServiceAccountName
74 |
75 | AdminServiceAccount="$AdminServiceAccountName@$project.iam.gserviceaccount.com"
76 | echo $AdminServiceAccount
77 |
78 | gcloud iam service-accounts create $AdminServiceAccountName --display-name "Admin service account for bootstrapping domain-joined servers with elevated permissions" --project $project
79 | gcloud iam service-accounts add-iam-policy-binding $AdminServiceAccount --member "user:$user" --role "roles/iam.serviceAccountUser" --project $project
80 | gcloud projects add-iam-policy-binding $project --member "serviceAccount:$AdminServiceAccount" --role "roles/editor"
81 |
82 | ServiceAccount=$AdminServiceAccount
83 | echo "Service Account: [$ServiceAccount]"
84 |
85 |
86 | gcloud kms keyrings create $kmsKeyRing --project $project --location $region
87 | gcloud kms keys create $kmsKey --project $project --purpose=encryption --keyring $kmsKeyRing --location $region
88 |
89 | sed "s/{Usr}/$user/g;s/{SvcAccount}/$ServiceAccount/g" policy.json | tee policy.out
90 | echo $policy
91 |
92 |
93 | gcloud kms keys set-iam-policy $kmsKey policy.out --project $project --location=$region --keyring=$kmsKeyRing
94 | rm policy.out
95 |
96 |
97 | sed "s/{Usr}/$user/g;s/{SvcAccount}/$DefaultServiceAccount/g" policy.json | tee policy.out
98 | gcloud kms keys set-iam-policy $kmsKey policy.out --project $project --location=$region --keyring=$kmsKeyRing
99 | rm policy.out
100 |
101 |
102 |
--------------------------------------------------------------------------------
/docs/ClickToDeploy.md:
--------------------------------------------------------------------------------
1 | # ClickToDeploy
2 |
3 | The clicktodeploy pattern is a one click deployment pattern used to deploy environments from the google marketplace. It requires a minimal number of inputs to define the environment, and with those it uses deployment manager, python (and powershell on windows) to automate the end-to-end deployment.
4 |
5 | For the original click to deploy search the google cloud marketplace for SQL Server 2016 AlwaysOn Failover cluster instance
6 |
7 | It requires 3 files:
8 | * windows-startup-script-ps1
9 | * defaulted on install
10 | * downloads the following 2 scripts to c:\C2D
11 | * runs c:\c2D\sql_install.ps1 as a schedulted task
12 | * sql_install.ps1 - downloaded from gs://c2d-windows/scripts/sqlserver
13 | * parameters come from metadata of the instance
14 | * c2d-property-sa-account : domain admin account
15 | * c2d-property-sa-password : domain admin password
16 | * c2d-property-domain-dns-name : Fully qualified domain name
17 | * sql-nodes : pipe delimited list of sql server nodes
18 | * states are stored in the following keys - create these keys yourself to skip steps
19 | * $sql_on_domain_reg = "HKLM:\SOFTWARE\Google\SQLOnDomain"
20 | * $sql_configured_reg = "HKLM:\SOFTWARE\Google\SQLServerConfigured"
21 | * $sql_server_task = "HKLM:\SOFTWARE\Google\SQLServerTask"
22 | * $shares_already_created_reg = "HKLM:\SOFTWARE\Google\SharesCreated"
23 | * WSFC cluster is setup on node 1
24 | * AlwaysOn is enabled
25 | * VIP is configured
26 | * c2d_base.psm1 - downloaded from gs://c2d-windows/scripts/
27 | * gce_base.psm1 - pre-installed to C:\Program Files\Google\Compute Engine\sysprep
28 |
29 | The following is the code that defines the downloading process.
30 |
31 | ``` Powershell
32 |
33 | $script:gce_install_dir = 'C:\Program Files\Google\Compute Engine\sysprep'
34 |
35 | $Script:c2d_scripts_bucket = 'c2d-windows/scripts'
36 | $Script:install_path="C:\C2D" # Folder for downloads
37 | $script:show_msgs = $false
38 | $script:write_to_serial = $false
39 |
40 | # Instance specific variables
41 | $script_name = 'sql_install.ps1'
42 | $script_subpath = 'sqlserver'
43 | $task_name = "SQLInstall"
44 |
45 | # Download the scripts
46 | # Base Script
47 | $base_script_path = "$Script:c2d_scripts_bucket/c2d_base.psm1"
48 | $base_script = "$Script:install_path\c2d_base.psm1"
49 |
50 | # Run Script
51 | $run_script = "$Script:install_path\$script_name"
52 | $run_script_path = "$Script:c2d_scripts_bucket/$script_subpath/$script_name"
53 | ```
54 |
55 | So...
56 | ClickToDeploy depends upon (preinstalled on all windows instances):
57 | 1. C:\Program Files\Google\Compute Engine\sysprep\gce_base.psm1
58 |
59 | We are downloading:
60 | 1. From "gs://c2d-windows/scripts/c2d_base.psm1"
61 | 2. To "C:\C2D\c2d_base.psm1"
62 | 3. From: "gs://c2d-windows/scripts/sqlserver/sql_install.ps1"
63 | 4. To: "C:\C2D\sql_install.ps1"
64 |
65 | These are provided for reference purposes in powershell/c2d
66 |
67 | ### gce_base.ps1 - This provides a library of functions for interfacing with GCE (pre-installed)
68 | * Get-Metadata
69 | * Generate-Random_Password
70 | * Write-Serial-Port
71 | * Write-Log
72 |
73 | ### c2d_base.ps1 - Library of c2d flow control libraries
74 | * Write-Logger - Write log messages to instance log
75 | * Write-ToReg - Write to registry
76 | * Runtime config functions for creating configs, variables and waiters
77 | * Functions for creating and deleting scheduled tasks
78 |
79 | ### sql_install.ps1 - Everything required to configure alwayson
80 | * Create a Windows Server Failover Cluster (WSFC)
81 | * Create an availability group
82 | * Create a database
83 | * Backup and restore a database from powershell
84 | * Create shared folders
85 | * Join a domain
86 | * Setup an entire cluster
87 |
88 | sql_install.ps1 gets called without any arguments from a scheduled task. It does the following:
89 | * SetScriptVar - Setup all the variables that will be consumed later in the setup
90 | * Reads the service account from c2d-property-sa-account
91 | * Reads domain name (Fully qualified) from c2d-property-domain-dns-name
92 | * Gets the Netbios domain by splitting domain on '.'
93 | * Reads sa password from c2d-property-sa-password
94 | * Reads list of nodes in cluster from sql-nodes into all_nodes
95 | * sets static ip addresses to 10.x.1.4
96 | * sets listener ip addresses to 10.x.1.5
97 | * it is assume the gateway and DC will always be 10.0.0.100
98 | * keep list of remote nodes (nodes this isnt running on) in remote_nodes
99 | * SetIP
100 | * Set IP addresses in Script:static_ip array
101 | * Set gateway to 10.0.0.100 in $Script:static_listner_ip
102 | * Add firewall rules for SQL server (1433) and AlwaysOn (5022) at windows level
103 |
104 | On all sql instances:
105 | ``` Powershell
106 | Install-WindowsFeature RSAT-AD-PowerShell
107 | Install-WindowsFeature Failover-Clustering -IncludeManagementTools
108 | ```
109 |
110 | ``` Powershell
111 | $Script:static_ip=@("10.10.0.3","10.10.0.4","10.10.0.5")
112 | $Script:cluster_name="cluster-dbclus"
113 | $Script:all_nodes_fqdn=@('c2d-sql-01.corp.acme.com','c2d-sql-02.corp.acme.com','c2d-sql-03.corp.acme.com')
114 | New-Cluster -Name $Script:cluster_name -Node $Script:all_nodes_fqdn -NoStorage -StaticAddress $Script:static_ip
115 |
116 | ```
117 |
--------------------------------------------------------------------------------
/docs/kms.md:
--------------------------------------------------------------------------------
1 | # Key Management Service
2 |
3 | We create a random password for local admin and safemode admin in primary-domain-controller-step-1.ps1. These are stored in SecureStrings.
4 |
5 | This project is depedent upon the creation of a kms ring and key that you will reference from the metadata.
6 |
7 | ```powershell
8 |
9 | $KmsKey = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/kms-key
10 | $GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
11 | $Region = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/region
12 | $Keyring = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/keyring
13 |
14 |
15 | $SafeModeAdminPassword = New-RandomPassword
16 | $LocalAdminPassword = New-RandomPassword
17 |
18 | Set-LocalUser Administrator -Password $LocalAdminPassword
19 | Enable-LocalUser Administrator
20 |
21 | Write-Host "Saving encrypted credentials in GCS..."
22 |
23 | $TempFile = New-TemporaryFile
24 |
25 | Unwrap-SecureString $LocalAdminPassword | gcloud kms encrypt --key $KmsKey --plaintext-file - --ciphertext-file $TempFile.FullName --location $Region --keyring $Keyring
26 | gsutil cp $TempFile.FullName "$GcsPrefix/output/domain-admin-password.bin"
27 |
28 | Unwrap-SecureString $SafeModeAdminPassword | gcloud kms encrypt --key $KmsKey --plaintext-file - --ciphertext-file $TempFile.FullName --location $Region --keyring $Keyring
29 | gsutil cp $TempFile.FullName "$GcsPrefix/output/dsrm-admin-password.bin"
30 |
31 | Remove-Item $TempFile.FullName -Force
32 |
33 | ```
34 | Now decrypt when you need it
35 | ```powershell
36 | $TempFile = New-TemporaryFile
37 |
38 | # invoke-command sees gsutil output as an error so redirect stderr to stdout and stringify to suppress
39 | gsutil cp $GcsPrefix/output/domain-admin-password.bin $TempFile.FullName 2>&1 | %{ "$_" }
40 |
41 | $DomainAdminPassword = $(gcloud kms decrypt --key $KmsKey --location $Region --keyring $Keyring --ciphertext-file $TempFile.FullName --plaintext-file - | ConvertTo-SecureString -AsPlainText -Force)
42 |
43 | Remove-Item $TempFile.FullName
44 | ```
45 |
46 | KMS is used to encrypt the password to a temporary file which is copied to cloud storage. This process is dependent upon the existence of a keyring and key in the specified region.
47 |
48 |
49 | ```bash
50 | #create a keyring
51 | gcloud kms keyrings create myring --location=us-central1
52 |
53 | #create an encryption key
54 | gcloud kms keys create mykey --location=us-central1 --keyring=myring --purpose=encryption
55 |
56 | #list rings
57 | gcloud kms keyrings list --location=us-central1
58 |
59 | NAME
60 | projects/{project-name}/locations/us-central1/keyRings/acme-deployment-ring
61 | projects/{project-name}/locations/us-central1/keyRings/acme-ring
62 |
63 |
64 | gcloud kms keys list --location=us-central1 --keyring=acme-deployment-ring
65 |
66 | NAME PURPOSE LABELS PRIMARY_ID PRIMARY_STATE
67 | projects/{project-name}/locations/us-central1/keyRings/acme-deployment-ring/cryptoKeys/acme-deployment-key ENCRYPT_DECRYPT 1 ENABLED
68 |
69 | ```
70 |
71 | In bash you can download the file as follows
72 | ```bash
73 | gsutil cp gs://acme-deployment/output/domain-admin-password.bin .
74 |
75 | gcloud kms decrypt --key acme-deployment-key --location us-central1 --keyring acme-deployment-ring --ciphertext-file
76 | domain-admin-password.bin --plaintext-file domain-admin-password.txt
77 |
78 | ```
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/docs/runtime-config.md:
--------------------------------------------------------------------------------
1 | #Runtime Config
2 |
3 | Runtime config variables are project scoped key value pairs that allow you to:
4 | * Dynamically configure services
5 | * Communicate service states
6 | * Send notification of changes to data
7 | * Share information between multiple tiers of services
8 |
9 | We create a runtime config resource in Terraform in the creation of the domain controller (in this case the variable value is "acme-runtime-config").
10 |
11 | ```terraform
12 | resource "google_runtimeconfig_config" "ad-runtime-config" {
13 | name = "${var.runtime-config}"
14 | description = "Runtime configuration values for my service"
15 | }
16 |
17 | ```
18 |
19 | This is created with the terraform apply at the time of creation of the domain controller and the will be the basis of a number of runtime config variables that will be used for the synchronisation of our deployment process. Most of it will be done from powershell which is where most of our windows configuration happens.
20 |
21 | The runtime config of a project can be found at the following full path:
22 |
23 | https://runtimeconfig.googleapis.com/v1beta1/projects/{project id}/configs/{runtime-config}.
24 |
25 | This is important because some methods reuire the full path to the config and variables while others do not. The clicktodeploy code which I am leveraging, requires that in metadata is a key value pair as follows:
26 | status-config-url:https://runtimeconfig.googleapis.com/v1beta1/projects/{project-name}/configs/acme-runtime-config
27 |
28 |
29 | # Get RuntimeConfig URL for the deployment
30 |
31 | THis is important because we cannot change code in c2d_base or gce_base as they are common public libraries. We have to provide the appropriate inputs which is the status-config-url metadata key.
32 |
33 | ### In c2d_base.ps1 (Imports gce_base.ps1)
34 | NOTE:
35 | * c2d_base.ps1 is downloaded from gs://c2d-windows/scripts to c:/c2d/ in install-sql-server-principal-step-1.ps1
36 | * gce_base.ps1 is in C:\Program Files\Google\Compute Engine\sysprep on all gce machines
37 |
38 |
39 | ```powershell
40 | $runtime_config = _FetchFromMetaData -property 'attributes/status-config-url'
41 |
42 | if ($runtime_config) {
43 | # Use second part of the config URL
44 | #run_time_base='https://runtimeconfig.googleapis.com/v1beta1'
45 | $config_name = (($runtime_config -split "$Script:run_time_base/")[1])
46 | return $config_name
47 | }
48 | else {
49 | Write-Log 'No RunTimeConfig found URL found in metadata.' -error
50 | return $false
51 | }
52 | ```
53 | The split results in $config_name=/projects/{project-name}/configs/acme-runtime-config
54 |
55 | We are now set up such that the sql_install.ps1 script will work.
56 |
57 |
58 |
59 | # After the domain controller installs
60 |
61 | We fetch the necessary variables from metadata and set the runtime config variable.
62 |
63 | ```powershell
64 |
65 | --flag completion of bootstrap requires beta gcloud component
66 | $projectId = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/project-id
67 | $RuntimeConfig = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/runtime-config
68 | $deploymentName = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/deployment-name
69 | $statusPath = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/status-variable-path
70 |
71 |
72 | Set-RuntimeConfigVariable -ConfigPath "projects/$projectId/configs/$RuntimeConfig" -Variable bootstrap/$deploymentName/$statusPath/success/time -Text (Get-Date -Format g)
73 |
74 | ```
75 |
76 | This results in a key of:
77 | "projects/{project-name}/configs/acme-runtime-config/variables/bootstrap/c2d/ad/success/time" having a value of the current time.
78 |
79 | # SQL Servers install WSFC and then wait.
80 |
81 | We can list the configs
82 | ```bash
83 | gcloud beta runtime-config configs list
84 |
85 | NAME DESCRIPTION
86 | acme-runtime-config Runtime configuration values for my service
87 | ```
88 |
89 | We can list the variables created by the process
90 | ```bash
91 | gcloud beta runtime-config configs variables list --config-name=acme-runtime-config
92 |
93 | NAME UPDATE_TIME
94 | backup/success/done 2018-12-05T23:38:55.792737384Z
95 | bootstrap/c2d/ad/success/time 2018-12-05T23:31:54.887933211Z
96 | cluster/success/done 2018-12-05T23:38:49.315840745Z
97 | initdb/success/done 2018-12-05T23:39:39.210801908Z
98 | replica/success/done 2018-12-05T23:39:56.925089787Z
99 | status/success/1768351525 2018-12-05T23:40:20.676334530Z
100 | status/success/2033934784 2018-12-05T23:40:19.905856083Z
101 | status/success/255883414 2018-12-05T23:40:40.431274139Z
102 | success/1710287108 2018-12-05T23:34:30.532767805Z
103 | success/825333265 2018-12-05T23:35:04.360200890Z
104 | success/865857694 2018-12-05T23:34:25.276051895Z
105 | ```
106 |
107 | We can list the waiters that were created to block while waiting for the domain to come up
108 |
109 | ```bash
110 | gcloud beta runtime-config configs waiters list --config-name=acme-runtime-config
111 | NAME CREATE_TIME WAITER_STATUS MESSAGE
112 | c2d-sql-01_waiter 2018-12-05T23:23:32 SUCCESS
113 | c2d-sql-02_waiter 2018-12-05T23:22:58 SUCCESS
114 | c2d-sql-03_waiter 2018-12-05T23:23:00 SUCCESS
115 | ```
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/modules/SQLServerWithStackdriver/main.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | data "google_project" "project" {
17 | }
18 |
19 | #Setup a template for the windows startup bat file. The bat files calls a powershell script so that is can get permissios to install stackdriver
20 | data "template_file" "windowsstartup" {
21 | template = file("../powershell/templates/windows-stackdriver-setup.ps1")
22 |
23 | vars = {
24 | environment = var.environment
25 | projectname = lower(data.google_project.project.name)
26 | computername = "${var.deployment-name}-${var.function}-${var.instancenumber}"
27 | }
28 | }
29 |
30 | locals {
31 | computername = "${var.deployment-name}-${var.function}-${var.instancenumber}"
32 | }
33 |
34 | resource "google_compute_disk" "datadisk" {
35 | name = "${local.computername}-pd-standard"
36 | zone = var.regionandzone
37 | type = "pd-standard"
38 | size = "200"
39 | }
40 |
41 | resource "google_compute_instance" "sqlserver" {
42 | name = local.computername
43 | machine_type = var.machinetype
44 | zone = var.regionandzone
45 | boot_disk {
46 | initialize_params {
47 | image = var.osimage
48 | size = "200"
49 | type = "pd-standard"
50 | }
51 | }
52 |
53 | network_interface {
54 | subnetwork = var.subnet-name
55 | alias_ip_range {
56 | ip_cidr_range = var.alwayson-vip
57 | }
58 | alias_ip_range {
59 | ip_cidr_range = var.wsfc-vip
60 | }
61 | access_config {
62 | // Ephemeral IP
63 | }
64 | }
65 |
66 | attached_disk {
67 | source = "${local.computername}-pd-standard"
68 | device_name = "appdata"
69 | }
70 |
71 | depends_on = [google_compute_disk.datadisk]
72 |
73 | tags = var.network-tag
74 |
75 | metadata = {
76 | environment = var.environment
77 | domain-name = var.domain-name
78 | domain-controller-address = var.domain-controller-address
79 | instancerole = var.instancerole
80 | function = var.function
81 | region = var.region
82 | keyring = var.keyring
83 | keyring-region = var.kms-region
84 | runtime-config = var.runtime-config
85 | kms-key = var.kms-key
86 | gcs-prefix = var.gcs-prefix
87 | netbios-name = var.netbios-name
88 | application = "SQLServer AlwaysOn"
89 | windows-startup-script-ps1 = data.template_file.windowsstartup.rendered
90 | role = var.instancerole
91 | wait-on = var.wait-on
92 | project-id = lower(data.google_project.project.project_id)
93 | post-join-script-url = var.post-join-script-url
94 | sql_nodes = var.sql_nodes
95 | }
96 |
97 | service_account {
98 | //scopes = ["storage-ro","monitoring-write","logging-write","trace-append"]
99 | scopes = ["cloud-platform", "https://www.googleapis.com/auth/cloudruntimeconfig", "storage-rw"]
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/modules/SQLServerWithStackdriver/vars.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | variable "alwayson-vip" {
17 | type = string
18 | description = "address where alwayson will listen"
19 | }
20 |
21 | variable "wsfc-vip" {
22 | type = string
23 | description = "address where wsfc will listen"
24 | }
25 |
26 | variable "machinetype" {
27 | type = string
28 | default = "n1-standard-4"
29 | }
30 |
31 | variable "osimage" {
32 | type = string
33 | }
34 |
35 | variable "environment" {
36 | type = string
37 | }
38 |
39 | variable "instancerole" {
40 | type = string
41 | default = "p"
42 | }
43 |
44 | variable "function" {
45 | type = string
46 | default = "sql"
47 | }
48 |
49 | variable "instancenumber" {
50 | type = string
51 | default = "01"
52 | }
53 |
54 | variable "regionandzone" {
55 | type = string
56 | }
57 |
58 | variable "deployment-name" {
59 | type = string
60 | default = ""
61 | }
62 |
63 | variable "assignedsubnet" {
64 | type = string
65 | default = "default"
66 | }
67 |
68 | variable "domain-name" {
69 | type = string
70 | default = "test-domain"
71 | }
72 |
73 | variable "kms-key" {
74 | type = string
75 | default = "p@ssword"
76 | }
77 |
78 | variable "kms-region" {
79 | type = string
80 | default = "us-central1"
81 | }
82 |
83 | variable "gcs-prefix" {
84 | type = string
85 | }
86 |
87 | variable "region" {
88 | type = string
89 | }
90 |
91 | variable "subnet-name" {
92 | type = string
93 | }
94 |
95 | variable "netbios-name" {
96 | type = string
97 | }
98 |
99 | variable "runtime-config" {
100 | type = string
101 | }
102 |
103 | variable "keyring" {
104 | type = string
105 | }
106 |
107 | variable "wait-on" {
108 | type = string
109 | }
110 |
111 | variable "domain-controller-address" {
112 | type = string
113 | }
114 |
115 | variable "status-variable-path" {
116 | type = string
117 | }
118 |
119 | variable "network-tag" {
120 | type = list(string)
121 | default = [""]
122 | description = "network tags"
123 | }
124 |
125 | variable "post-join-script-url" {
126 | type = string
127 | default = ""
128 | description = "after joining to the domain"
129 | }
130 |
131 | variable "sql_nodes" {
132 | type = string
133 | default = ""
134 | description = "list of sql nodes in cluster"
135 | }
136 |
137 |
--------------------------------------------------------------------------------
/modules/SQLServerWithStackdriver/versions.tf:
--------------------------------------------------------------------------------
1 |
2 | terraform {
3 | required_version = ">= 0.12"
4 | }
5 |
--------------------------------------------------------------------------------
/modules/network/main.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | resource "google_compute_subnetwork" "primary-subnetwork" {
17 | name = "${var.network-name}-subnet-1"
18 | ip_cidr_range = "${var.primary-cidr}"
19 | region = "${var.primary-region}"
20 | network = "${google_compute_network.custom-network.self_link}"
21 | }
22 |
23 | resource "google_compute_subnetwork" "subnetwork-2" {
24 | name = "${var.network-name}-subnet-2"
25 | ip_cidr_range = "${var.second-cidr}"
26 | region = "${var.primary-region}"
27 | network = "${google_compute_network.custom-network.self_link}"
28 | }
29 |
30 | resource "google_compute_subnetwork" "subnetwork-3" {
31 | name = "${var.network-name}-subnet-3"
32 | ip_cidr_range = "${var.third-cidr}"
33 | region = "${var.primary-region}"
34 | network = "${google_compute_network.custom-network.self_link}"
35 | }
36 |
37 | resource "google_compute_subnetwork" "subnetwork-4" {
38 | name = "${var.network-name}-subnet-4"
39 | ip_cidr_range = "${var.fourth-cidr}"
40 | region = "${var.dr-region}"
41 | network = "${google_compute_network.custom-network.self_link}"
42 | }
43 |
44 | resource "google_compute_network" "custom-network" {
45 | name = "${var.network-name}"
46 | auto_create_subnetworks = false
47 | }
48 |
49 | resource "google_compute_firewall" "default" {
50 | name = "${var.deployment-name}-allow-remote-access"
51 | network = "${google_compute_network.custom-network.self_link}"
52 |
53 |
54 | allow {
55 | protocol = "tcp"
56 | ports = ["3389", "8080"]
57 | }
58 |
59 | source_ranges = ["35.185.218.131/32"]
60 | target_tags = ["web","pdc","sql"]
61 | }
62 |
63 | resource "google_compute_firewall" "allow-internal" {
64 | name = "${var.deployment-name}-allow-internal"
65 | network = "${google_compute_network.custom-network.self_link}"
66 |
67 | allow {
68 | protocol = "all"
69 | }
70 |
71 | source_tags = ["pdc","sql"]
72 | target_tags = ["sql","pdc"]
73 | }
74 |
75 | resource "google_compute_firewall" "healthchecks" {
76 | name = "${var.deployment-name}-allow-healthcheck-access"
77 | network = "${google_compute_network.custom-network.self_link}"
78 |
79 |
80 | allow {
81 | protocol = "tcp"
82 | ports = ["1-65535"]
83 | }
84 |
85 | source_ranges = ["130.211.0.0/22","35.191.0.0/16"]
86 | target_tags = ["sql"]
87 | }
88 |
89 | resource "google_compute_firewall" "alwayson" {
90 | name = "${var.deployment-name}-allow-alwayson-access"
91 | network = "${google_compute_network.custom-network.self_link}"
92 |
93 |
94 | allow {
95 | protocol = "tcp"
96 | ports = ["5022"]
97 | }
98 |
99 | source_tags = ["sql"]
100 | target_tags = ["sql"]
101 | }
102 |
--------------------------------------------------------------------------------
/modules/network/outputs.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | output "subnet-name" {
17 | value = "${google_compute_subnetwork.primary-subnetwork.name}"
18 | #value = "test-string"
19 | }
20 | output "second-subnet-name" {
21 | value = "${google_compute_subnetwork.subnetwork-2.name}"
22 | #value = "test-string"
23 | }
24 | output "third-subnet-name" {
25 | value = "${google_compute_subnetwork.subnetwork-3.name}"
26 | #value = "test-string"
27 | }
28 | output "fourth-subnet-name" {
29 | value = "${google_compute_subnetwork.subnetwork-4.name}"
30 | #value = "test-string"
31 | }
32 |
--------------------------------------------------------------------------------
/modules/network/vars.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | variable "network-name" {
17 | type = "string"
18 | default = "custom-network"
19 | }
20 | variable "primary-cidr" {
21 | type = "string"
22 | default = "10.10.1.0/16"
23 | }
24 | variable "second-cidr" {
25 | type = "string"
26 | default = "10.11.1.0/16"
27 | }
28 | variable "third-cidr" {
29 | type = "string"
30 | default = "10.12.1.0/16"
31 | }
32 | variable "fourth-cidr" {
33 | type = "string"
34 | default = "10.13.1.0/16"
35 | }
36 | variable "deployment-name" {
37 | type = "string"
38 | default = "depl"
39 | }
40 | variable "primary-region" {
41 | type = "string"
42 | default = "us-central1"
43 | }
44 | variable "dr-region" {
45 | type = "string"
46 | default = "us-east1"
47 | }
48 |
--------------------------------------------------------------------------------
/modules/windowsDCWithStackdriver/main.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | data "google_project" "project" {
17 | }
18 |
19 | #Setup a template for the windows startup bat file. The bat files calls a powershell script so that is can get permissios to install stackdriver
20 | data "template_file" "windowsstartup" {
21 | template = file("../powershell/templates/windows-stackdriver-setup.ps1")
22 |
23 | vars = {
24 | environment = var.environment
25 | projectname = lower(data.google_project.project.name)
26 | computername = "${var.deployment-name}-${var.function}-${var.instancenumber}"
27 | }
28 | }
29 |
30 | locals {
31 | computername = "${var.deployment-name}-${var.function}-${var.instancenumber}"
32 | }
33 |
34 | resource "google_compute_disk" "datadisk" {
35 | name = "${local.computername}-pd-standard"
36 | zone = var.regionandzone
37 | type = "pd-standard"
38 | size = "200"
39 | }
40 |
41 | resource "google_runtimeconfig_config" "ad-runtime-config" {
42 | name = var.runtime-config
43 | description = "Runtime configuration values for my service"
44 | }
45 |
46 | resource "google_compute_instance" "domain-controller" {
47 | name = local.computername
48 | machine_type = var.machinetype
49 | zone = var.regionandzone
50 |
51 | boot_disk {
52 | initialize_params {
53 | image = var.osimage
54 | size = "200"
55 | type = "pd-standard"
56 | }
57 | }
58 |
59 | network_interface {
60 | subnetwork = var.subnet-name
61 | network_ip = var.network-ip
62 | access_config {
63 | }
64 | }
65 |
66 | attached_disk {
67 | source = "${local.computername}-pd-standard"
68 | device_name = "appdata"
69 | }
70 |
71 | depends_on = [google_compute_disk.datadisk]
72 |
73 | tags = var.network-tag
74 |
75 | metadata = {
76 | environment = var.environment
77 | domain-name = var.domain-name
78 | function = var.function
79 | region = var.region
80 | keyring = var.keyring
81 | runtime-config = var.runtime-config
82 | deployment-name = var.deployment-name
83 | kms-key = var.kms-key
84 | keyring-region = var.kms-region
85 | gcs-prefix = var.gcs-prefix
86 | netbios-name = var.netbios-name
87 | application = "primary domain controller"
88 | windows-startup-script-ps1 = data.template_file.windowsstartup.rendered
89 | role = var.instancerole
90 | status-variable-path = var.status-variable-path
91 | project-id = lower(data.google_project.project.project_id)
92 | }
93 |
94 | service_account {
95 | //scopes = ["storage-ro","monitoring-write","logging-write","trace-append"]
96 | scopes = ["https://www.googleapis.com/auth/cloud-platform"]
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/modules/windowsDCWithStackdriver/outputs.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | locals {
17 | nic = google_compute_instance.domain-controller.network_interface[0]
18 | }
19 |
20 | output "dc-address" {
21 | value = local.nic["network_ip"]
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/modules/windowsDCWithStackdriver/vars.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | variable "machinetype" {
17 | type = string
18 | default = "n1-standard-8"
19 | }
20 |
21 | variable "osimage" {
22 | type = string
23 | }
24 |
25 | variable "environment" {
26 | type = string
27 | }
28 |
29 | variable "instancerole" {
30 | type = string
31 | default = "p"
32 | }
33 |
34 | variable "function" {
35 | type = string
36 | default = "pdc"
37 | }
38 |
39 | variable "instancenumber" {
40 | type = string
41 | default = "01"
42 | }
43 |
44 | variable "regionandzone" {
45 | type = string
46 | }
47 |
48 | variable "deployment-name" {
49 | type = string
50 | default = ""
51 | }
52 |
53 | variable "assignedsubnet" {
54 | type = string
55 | default = "default"
56 | }
57 |
58 | variable "domain-name" {
59 | type = string
60 | default = "test-domain"
61 | }
62 |
63 | variable "kms-key" {
64 | type = string
65 | default = "p@ssword"
66 | }
67 |
68 | variable "kms-region" {
69 | type = string
70 | default = "us-central1"
71 | }
72 |
73 | variable "gcs-prefix" {
74 | type = string
75 | }
76 |
77 | variable "region" {
78 | type = string
79 | }
80 |
81 | variable "subnet-name" {
82 | type = string
83 | }
84 |
85 | variable "secondary-subnet-name" {
86 | type = string
87 | }
88 |
89 | variable "netbios-name" {
90 | type = string
91 | }
92 |
93 | variable "runtime-config" {
94 | type = string
95 | }
96 |
97 | variable "keyring" {
98 | type = string
99 | }
100 |
101 | variable "wait-on" {
102 | type = string
103 | }
104 |
105 | variable "status-variable-path" {
106 | type = string
107 | }
108 |
109 | variable "network-tag" {
110 | type = list(string)
111 | default = [""]
112 | description = "network tags"
113 | }
114 |
115 | variable "network-ip" {
116 | type = string
117 | default = ""
118 | }
119 |
120 | #variable "project-id" {
121 | # type="string"
122 | #}
123 |
--------------------------------------------------------------------------------
/modules/windowsDCWithStackdriver/versions.tf:
--------------------------------------------------------------------------------
1 |
2 | terraform {
3 | required_version = ">= 0.12"
4 | }
5 |
--------------------------------------------------------------------------------
/modules/windowsWithStackdriver/main.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | data "google_project" "project" {
17 | }
18 |
19 | #Setup a template for the windows startup bat file. The bat files calls a powershell script so that is can get permissios to install stackdriver
20 | data "template_file" "windowsstartup" {
21 | template = file("../powershell/windows-stackdriver-setup.ps1")
22 |
23 | vars = {
24 | environment = var.environment
25 | projectname = lower(data.google_project.project.name)
26 | computername = "${var.deployment-name}-${var.environment}-${var.function}-${var.instancerole}-${var.instancenumber}"
27 | }
28 | }
29 |
30 | resource "google_compute_disk" "datadisk" {
31 | name = "${var.deployment-name}-${var.environment}-${var.function}-${var.instancerole}-${var.instancenumber}-pd-standard"
32 | zone = var.regionandzone
33 | type = "pd-standard"
34 | size = "200"
35 | }
36 |
37 | resource "google_compute_instance" "windows" {
38 | name = "${var.deployment-name}-${var.environment}-${var.function}-${var.instancerole}-${var.instancenumber}"
39 | machine_type = var.machinetype
40 | zone = var.regionandzone
41 | boot_disk {
42 | initialize_params {
43 | image = var.osimage
44 | size = "200"
45 | type = "pd-standard"
46 | }
47 | }
48 |
49 | network_interface {
50 | subnetwork = var.subnet-name
51 | access_config {
52 | // Ephemeral IP
53 | }
54 | }
55 |
56 | //network_interface {
57 | // network="default"
58 | //subnetwork = "${var.secondary-subnet-name}"
59 | // access_config {
60 | // Ephemeral IP
61 | // }
62 | // }
63 |
64 | attached_disk {
65 | source = "${var.deployment-name}-${var.environment}-${var.function}-${var.instancerole}-${var.instancenumber}-pd-standard"
66 | device_name = "appdata"
67 | }
68 |
69 | depends_on = [google_compute_disk.datadisk]
70 |
71 | tags = var.network-tag
72 |
73 | metadata = {
74 | environment = var.environment
75 | domain-name = var.domain-name
76 | function = var.function
77 | region = var.region
78 | keyring = var.keyring
79 | runtime-config = var.runtime-config
80 | kms-key = var.kms-key
81 | gcs-prefix = var.gcs-prefix
82 | netbios-name = var.netbios-name
83 | application = "basicwindows"
84 | windows-startup-script-ps1 = data.template_file.windowsstartup.rendered
85 | role = var.instancerole
86 | }
87 |
88 | service_account {
89 | //scopes = ["storage-ro","monitoring-write","logging-write","trace-append"]
90 | scopes = ["https://www.googleapis.com/auth/cloud-platform"]
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/modules/windowsWithStackdriver/vars.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | variable "machinetype" {
17 | type = string
18 | default = "n1-standard-4"
19 | }
20 |
21 | variable "osimage" {
22 | type = string
23 | }
24 |
25 | variable "environment" {
26 | type = string
27 | }
28 |
29 | variable "instancerole" {
30 | type = string
31 | default = "p"
32 | }
33 |
34 | variable "function" {
35 | type = string
36 | default = "pdc"
37 | }
38 |
39 | variable "instancenumber" {
40 | type = string
41 | default = "01"
42 | }
43 |
44 | variable "regionandzone" {
45 | type = string
46 | }
47 |
48 | variable "deployment-name" {
49 | type = string
50 | default = ""
51 | }
52 |
53 | variable "assignedsubnet" {
54 | type = string
55 | default = "default"
56 | }
57 |
58 | variable "domain-name" {
59 | type = string
60 | default = "test-domain"
61 | }
62 |
63 | variable "kms-key" {
64 | type = string
65 | default = "p@ssword"
66 | }
67 |
68 | variable "gcs-prefix" {
69 | type = string
70 | }
71 |
72 | variable "region" {
73 | type = string
74 | }
75 |
76 | variable "subnet-name" {
77 | type = string
78 | }
79 |
80 | variable "secondary-subnet-name" {
81 | type = string
82 | }
83 |
84 | variable "netbios-name" {
85 | type = string
86 | }
87 |
88 | variable "runtime-config" {
89 | type = string
90 | }
91 |
92 | variable "keyring" {
93 | type = string
94 | }
95 |
96 | variable "wait-on" {
97 | type = string
98 | }
99 |
100 | variable "domain-controller-address" {
101 | type = string
102 | }
103 |
104 | variable "network-tag" {
105 | type = list(string)
106 | default = [""]
107 | description = "network tags"
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/modules/windowsWithStackdriver/versions.tf:
--------------------------------------------------------------------------------
1 |
2 | terraform {
3 | required_version = ">= 0.12"
4 | }
5 |
--------------------------------------------------------------------------------
/modules/wsus/main.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | data "google_project" "project" {}
17 | data "template_file" "windowsstartupwsus" {
18 | template = "${file("../powershell/windowsstartupwsus.ps1")}"
19 | vars {
20 | environment = "${var.environment}"
21 | dnsserver1 = "${var.dnsserver1}"
22 | dnsserver2 = "${var.dnsserver2}"
23 | projectname = "${lower(data.google_project.project.name)}"
24 | number = "${var.instancenumber}"
25 | }
26 | }
27 | resource "google_compute_instance" "wsus"{
28 | name = "${var.deployment-name}${var.environment}-${var.instancerole}-wsus${var.instancenumber}"
29 | machine_type = "${var.machinetype}"
30 | zone = "${var.regionandzone}"
31 | boot_disk {
32 | initialize_params
33 | {
34 | image = "${var.osimage}"
35 | size = "400"
36 | type = "pd-standard"
37 | }
38 | }
39 | network_interface {
40 | subnetwork = "${lower(data.google_project.project.name)}-vpc-${substr(var.regionandzone,0,length(var.regionandzone)-2)}-${var.assignedsubnet}"
41 | #subnetwork = "default"
42 | access_config {
43 | // Ephemeral IP
44 | }
45 | }
46 | tags = ["windowsupdate-server"]
47 | metadata {
48 | applicationstack="wsus"
49 | environment = "${var.environment}"
50 | application = "wsus"
51 | windows-startup-script-ps1 = "${data.template_file.windowsstartupwsus.rendered}"
52 | role = "${var.instancerole}"
53 | }
54 | service_account {
55 | scopes = ["storage-ro","monitoring-write","logging-write","trace-append"]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/modules/wsus/vars.tf:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | variable "machinetype" {type = "string" default = "n1-standard-4" }
17 | variable "osimage" {type = "string" default = "windows-server-2016-dc-v20180710"}
18 | variable "environment" {type = "string" }
19 | variable "instancerole" {type = "string" default = "p"}
20 | variable "instancenumber" {type = "string" default = "01"}
21 | variable "regionandzone" {type = "string"}
22 | variable "deployment-name" {type = "string" default = ""}
23 | variable "dnsserver1" {type = "string" default = ""}
24 | variable "dnsserver2" {type = "string" default = ""}
25 |
--------------------------------------------------------------------------------
/powershell/bootstrap/domain-member.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Function Unwrap-SecureString() {
18 | Param(
19 | [System.Security.SecureString] $SecureString
20 | )
21 | Return (New-Object -TypeName System.Net.NetworkCredential -ArgumentList '', $SecureString).Password
22 | }
23 |
24 | Function Set-RuntimeConfigVariable {
25 | Param(
26 | [Parameter(Mandatory=$True)][String] $ConfigPath,
27 | [Parameter(Mandatory=$True)][String] $Variable,
28 | [Parameter(Mandatory=$True)][String] $Text
29 | )
30 |
31 | $Auth = $(gcloud auth print-access-token)
32 |
33 | $Path = "$ConfigPath/variables"
34 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$Path"
35 |
36 | $Json = (@{
37 | name = "$Path/$Variable"
38 | text = $Text
39 | } | ConvertTo-Json)
40 |
41 | $Headers = @{
42 | Authorization = "Bearer " + $Auth
43 | }
44 |
45 | $Params = @{
46 | Method = "POST"
47 | Headers = $Headers
48 | ContentType = "application/json"
49 | Uri = $Url
50 | Body = $Json
51 | }
52 |
53 | Try {
54 | Return Invoke-RestMethod @Params
55 | }
56 | Catch {
57 | $Reader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
58 | $ErrResp = $Reader.ReadToEnd() | ConvertFrom-Json
59 | $Reader.Close()
60 | Return $ErrResp
61 | }
62 |
63 | }
64 |
65 | Function Get-RuntimeConfigWaiter {
66 | Param(
67 | [Parameter(Mandatory=$True)][String] $ConfigPath,
68 | [Parameter(Mandatory=$True)][String] $Waiter
69 | )
70 |
71 | $Auth = $(gcloud auth print-access-token)
72 |
73 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters/$Waiter"
74 | $Headers = @{
75 | Authorization = "Bearer " + $Auth
76 | }
77 | $Params = @{
78 | Method = "GET"
79 | Headers = $Headers
80 | Uri = $Url
81 | }
82 |
83 | Return Invoke-RestMethod @Params
84 | }
85 |
86 | Function Wait-RuntimeConfigWaiter {
87 | Param(
88 | [Parameter(Mandatory=$True)][String] $ConfigPath,
89 | [Parameter(Mandatory=$True)][String] $Waiter,
90 | [int] $Sleep = 60
91 | )
92 | $RuntimeWaiter = $Null
93 | While (($RuntimeWaiter -eq $Null) -Or (-Not $RuntimeWaiter.done)) {
94 | $RuntimeWaiter = Get-RuntimeConfigWaiter -ConfigPath $ConfigPath -Waiter $Waiter
95 | If (-Not $RuntimeWaiter.done) {
96 | Write-Host "Waiting for [$ConfigPath/waiters/$Waiter]..."
97 | Sleep $Sleep
98 | }
99 | }
100 | Return $RuntimeWaiter
101 | }
102 |
103 | Function New-RandomString {
104 | Param(
105 | [int] $Length = 10,
106 | [char[]] $AllowedChars = $Null
107 | )
108 | If ($AllowedChars -eq $Null) {
109 | (,(33,126)) | % { For ($a=$_[0]; $a -le $_[1]; $a++) { $AllowedChars += ,[char][byte]$a } }
110 | }
111 | For ($i=1; $i -le $Length; $i++) {
112 | $Temp += ( $AllowedChars | Get-Random )
113 | }
114 | Return $Temp
115 | }
116 |
117 | Function Create-RuntimeConfigWaiter {
118 | Param(
119 | [Parameter(Mandatory=$True)][String] $ConfigPath,
120 | [Parameter(Mandatory=$True)][String] $Waiter,
121 | [Parameter(Mandatory=$True)][String] $Timeout,
122 | [Parameter(Mandatory=$True)][String] $SuccessPath,
123 | [Parameter(Mandatory=$True)][Int] $SuccessCardinality,
124 | [Parameter(Mandatory=$False)][String] $FailurePath = "",
125 | [Parameter(Mandatory=$False)][Int] $FailureCardinality=0
126 | )
127 |
128 | $RuntimeWaiter = $Null
129 |
130 | Write-Host $ConfigPath/waiters/$Waiter
131 |
132 | $Auth = $(gcloud auth print-access-token)
133 |
134 |
135 | if($FailurePath.Length -eq 0){
136 | $Body = "{timeout: '" + $Timeout + "s', name: '$ConfigPath/waiters/$Waiter', success: { cardinality: { number: $SuccessCardinality, path: '$SuccessPath' }}}"
137 | }else{
138 | $Body = "{timeout: '" + $Timeout + "s', name: '$ConfigPath/waiters/$Waiter', `
139 | success: { cardinality: { number: $SuccessCardinality, path: '$SuccessPath' }}, `
140 | failure: { cardinality: { number: $FailureCardinality, path: '$FailurePath' }}}"
141 | }
142 |
143 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters"
144 |
145 | $Headers = @{
146 | Authorization="Bearer " + $Auth
147 | }
148 | $Params = @{
149 | Method = "POST"
150 | Headers = $Headers
151 | Uri = $Url
152 | Body=$Body
153 | }
154 | Write-Host "$Url"
155 | # Write-Host "$Params"
156 |
157 | #Return Invoke-RestMethod $Params
158 | Return Invoke-RestMethod -Uri $Url -Headers $Headers -Method 'Post' -Body $Body -ContentType "application/json"
159 | #Return $RuntimeWaiter
160 | }
161 |
162 | Function Delete-RuntimeConfigWaiter {
163 | Param(
164 | [Parameter(Mandatory=$True)][String] $ConfigPath,
165 | [Parameter(Mandatory=$True)][String] $Waiter
166 | )
167 |
168 | $RuntimeWaiter = $Null
169 |
170 | Write-Host $ConfigPath/waiters/$Waiter
171 |
172 | $Auth = $(gcloud auth print-access-token)
173 |
174 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters/$Waiter"
175 |
176 | $Headers = @{
177 | Authorization="Bearer " + $Auth
178 | }
179 |
180 | Write-Host "$Url"
181 |
182 | Return Invoke-RestMethod -Uri $Url -Headers $Headers -Method 'Delete'
183 | }
184 |
185 | Function New-RandomPassword() {
186 | Param(
187 | [int] $Length = 16,
188 | [char[]] $AllowedChars = $Null
189 | )
190 | Return New-RandomString -Length $Length -AllowedChars $AllowedChars | ConvertTo-SecureString -AsPlainText -Force
191 | }
192 |
193 |
194 |
195 | Function Get-GoogleMetadata() {
196 | Param (
197 | [Parameter(Mandatory=$True)][String] $Path
198 | )
199 | Try {
200 | Return Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/$Path
201 | }
202 | Catch {
203 | Return $Null
204 | }
205 | }
206 |
207 |
208 | Write-Host "Bootstrap script started..."
209 |
210 |
211 | $name = Get-GoogleMetadata "instance/name"
212 | $zone = Get-GoogleMetadata "instance/zone"
213 |
214 | If ("true" -like (Get-GoogleMetadata "instance/attributes/remove-address")) {
215 | Write-Host "Removing external address..."
216 | gcloud compute instances delete-access-config $name --zone $zone
217 | }
218 |
219 |
220 | Write-Host "Adding AD powershell tools..."
221 | Add-WindowsFeature RSAT-AD-PowerShell
222 |
223 | # the path to the runtime-config must be the full path ie.
224 | # "projects/{project-name}/configs/acme-runtime-config"
225 | # the success path is what is in wait-on
226 | # waiter name does not need to be passed in
227 | $Waiter = $name + '_waiter'
228 |
229 | $Deployment = Get-GoogleMetadata "instance/attributes/deployment-name"
230 | $SuccessPath = Get-GoogleMetadata "instance/attributes/wait-on"
231 | $ProjectId = Get-GoogleMetadata "/instance/attributes/project-id"
232 | $RuntimeConfig = Get-GoogleMetadata "instance/attributes/runtime-config"
233 | $FullRTConfigPath = "projects/$ProjectId/configs/$RuntimeConfig"
234 |
235 | Write-Host "Runtime-config waiter is $Waiter"
236 | Write-Host "Success path is $SuccessPath"
237 | Write-Host "Full runtime-config path is: $FullRTConfigPath"
238 |
239 | If ($SuccessPath) {
240 | Write-Host "Waiting for $SuccessPath..."
241 | Write-Host "Config $RuntimeConfig and waiter: $Waiter"
242 |
243 | $result = Create-RuntimeConfigWaiter $FullRTConfigPath `
244 | $Waiter `
245 | 1800 `
246 | $SuccessPath `
247 | 1
248 |
249 | Write-Host $result
250 |
251 | Wait-RuntimeConfigWaiter -ConfigPath $FullRTConfigPath -Waiter $Waiter
252 | }
253 |
254 |
255 | Write-Host "Configuring network..."
256 | $DomainControllerAddresses = Get-GoogleMetadata "instance/attributes/domain-controller-address"
257 | # set dns to domain controller
258 | Set-DnsClientServerAddress -InterfaceAlias Ethernet -ServerAddresses $DomainControllerAddresses
259 |
260 | Write-Host "Configuring local admin..."
261 | # startup script runs as local system which cannot join domain
262 | # so do the join as local administrator using random password
263 | $LocalAdminPassword = New-RandomPassword
264 | Set-LocalUser Administrator -Password $LocalAdminPassword
265 | Enable-LocalUser Administrator
266 |
267 | $LocalAdminCredentials = New-Object `
268 | -TypeName System.Management.Automation.PSCredential `
269 | -ArgumentList "\Administrator",$LocalAdminPassword
270 | Invoke-Command -Credential $LocalAdminCredentials -ComputerName . -ScriptBlock {
271 |
272 | Write-Host "Getting job metadata..."
273 | $Domain = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/domain-name
274 | $NetBiosName = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/netbios-name
275 | $KmsKey = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/kms-key
276 | $KmsRegion = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/keyring-region
277 | $Region = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/region
278 | $Keyring = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/keyring
279 | $GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
280 |
281 | Write-Host "Fetching admin credentials..."
282 |
283 | # fetch domain admin credentials
284 | If ($GcsPrefix.EndsWith("/")) {
285 | $GcsPrefix = $GcsPrefix -Replace ".$"
286 | }
287 | $TempFile = New-TemporaryFile
288 |
289 | # invoke-command sees gsutil output as an error so redirect stderr to stdout and stringify to suppress
290 | gsutil cp $GcsPrefix/output/domain-admin-password.bin $TempFile.FullName 2>&1 | %{ "$_" }
291 |
292 | $DomainAdminPassword = $(gcloud kms decrypt --key $KmsKey --location $KmsRegion --keyring $Keyring --ciphertext-file $TempFile.FullName --plaintext-file - | ConvertTo-SecureString -AsPlainText -Force)
293 |
294 | Remove-Item $TempFile.FullName
295 |
296 | <#$DomainAdminCredentials = New-Object `
297 | -TypeName System.Management.Automation.PSCredential `
298 | -ArgumentList "$NetBiosName\Administrator",$DomainAdminPassword#>
299 | Write-Host "Domain is $Domain"
300 |
301 | $DomainAdminCredentials = New-Object `
302 | -TypeName System.Management.Automation.PSCredential `
303 | -ArgumentList "$Domain\Administrator", $DomainAdminPassword
304 |
305 | Write-Host "Joining domain... using credential $DomainAdminCredentials"
306 | Add-Computer -DomainName $Domain -Credential $DomainAdminCredentials
307 |
308 | $RuntimeConfig = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/runtime-config
309 | Write-Host "Runtime config is $RuntimeConfig"
310 |
311 | #Now write the status-config-url for the c2d scripts
312 | #It needs to be the full path
313 | $Script:run_time_base = 'https://runtimeconfig.googleapis.com/v1beta1'
314 | $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
315 | $zone = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/zone
316 | $projectId = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/project-id
317 |
318 | gcloud compute instances add-metadata "$name" --zone $zone --metadata "status-config-url=$Script:run_time_base/projects/$projectId/configs/$RuntimeConfig"
319 |
320 | Write-Host "setting status-config-url=$Script:run_time_base/projects/$projectId/configs/$RuntimeConfig"
321 |
322 | try {
323 | Write-Host "Done adding to domain, adding key to reg: HKLM:\SOFTWARE\Google\SQLOnDomain"
324 | $result = New-Item -Path "HKLM:\SOFTWARE\Google\SQLOnDomain" -Force
325 | }
326 | catch [System.IO.IOException] {
327 | Write-Log "$_.Exception.Message"
328 | Write-Log "Error writing to registry $result"
329 | }
330 | }
331 |
332 |
333 | $PostJoinScriptUrl = Get-GoogleMetadata "instance/attributes/post-join-script-url"
334 | If ($PostJoinScriptUrl) {
335 |
336 | Write-Host "Configuring startup metadata for post-join script..."
337 | # set post join url as startup script then restart
338 | $name = Get-GoogleMetadata "instance/name"
339 | $zone = Get-GoogleMetadata "instance/zone"
340 | gcloud compute instances add-metadata "$name" --zone $zone --metadata "windows-startup-script-url=$PostJoinScriptUrl"
341 |
342 | Write-Host "Restarting..."
343 | Restart-Computer
344 |
345 | }
346 | Else {
347 |
348 | Write-Host "Configuring startup metadata..."
349 | # remove startup script from metadata to prevent rerun on reboot
350 | $name = Get-GoogleMetadata "instance/name"
351 | $zone = Get-GoogleMetadata "instance/zone"
352 | gcloud compute instances remove-metadata "$name" --zone $zone --keys windows-startup-script-url
353 |
354 | Write-Host "Signaling completion..."
355 |
356 | # flag completion of bootstrap requires beta gcloud component
357 | $name = Get-GoogleMetadata "instance/name"
358 | $RuntimeConfig = Get-GoogleMetadata "instance/attributes/runtime-config"
359 |
360 | Set-RuntimeConfigVariable -ConfigPath $RuntimeConfig -Variable bootstrap/$name/success/time -Text (Get-Date -Format g)
361 |
362 | }
363 |
--------------------------------------------------------------------------------
/powershell/bootstrap/initial_alwayson_startup_script.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 |
18 | ####This script is copied into the end of install_sql_server_principal-step_1.ps1
19 | Set-StrictMode -Version Latest
20 |
21 | $script:gce_install_dir = 'C:\Program Files\Google\Compute Engine\sysprep'
22 |
23 | # Import Modules
24 | try {
25 | Import-Module $script:gce_install_dir\gce_base.psm1 -ErrorAction Stop
26 | }
27 | catch [System.Management.Automation.ActionPreferenceStopException] {
28 | Write-Host $_.Exception.GetBaseException().Message
29 | Write-Host ("Unable to import GCE module from $script:gce_install_dir. " +
30 | 'Check error message, or ensure module is present.')
31 | exit 2
32 | }
33 |
34 | # Default Values
35 | $Script:c2d_scripts_bucket = 'c2d-windows/scripts'
36 | $Script:tf_scripts_bucket = '{bucket}/powershell/bootstrap'
37 | $Script:install_path="C:\C2D" # Folder for downloads
38 | $script:show_msgs = $false
39 | $script:write_to_serial = $false
40 |
41 |
42 | # Functions
43 | function DownloadScript {
44 | <#
45 | .SYNOPSIS
46 | Downloads a script to the localmachine from GCS.
47 | .DESCRIPTION
48 | Uses WebClient to download a script file.
49 | .EXAMPLE
50 | DownloadScript -path bucket/.. -filename
51 | #>
52 | param (
53 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
54 | $path,
55 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
56 | $filename,
57 | [Switch] $overwrite
58 | )
59 | $storage_url = 'http://storage.googleapis.com'
60 | $download_url = "$storage_url/$path"
61 |
62 |
63 | # Check if file already exists and act accordingly.
64 | if ((Test-path -path $filename)){
65 | if ($overwrite){
66 | Write-Log "$filename already exists. Overwrite flag set."
67 | _DeleteFiles -files $filename
68 | }
69 | else {
70 | Write-Log "$filename already exists. Overwrite flag notset."
71 | return $true
72 | }
73 | }
74 | # Download the file
75 | Write-Log "Original download url: $download_url"
76 | # To avoid cache issues
77 | $url = $download_url + "?random=" + (Get-Random).ToString()
78 |
79 | Write-Log "Downloading $url to $filename"
80 | try {
81 | Invoke-WebRequest -Uri $url -OutFile $filename -Headers @{"Cache-Control"="private"}
82 | }
83 | catch [System.Net.WebException] {
84 | $response = $_.Exception.Response
85 | if ($response) {
86 | _PrintError
87 | Write-Log $response.StatusCode -error # This is a System.Net.HttpStatusCode enum value
88 | Write-Log $response.StatusCode.value__ -error # This is the numeric version.
89 | }
90 | else {
91 | $type = $_.Exception.GetType().FullName
92 | $message = $_.Exception.Message
93 | Write-Log "$type $message"
94 | }
95 | return $false
96 | }
97 |
98 | # Check if download successfull
99 | if ((Test-path -path $filename)){
100 | return $true
101 | }
102 | else {
103 | Write-Log "File not found."
104 | return $false
105 | }
106 | }
107 |
108 | $GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
109 | $script_bucket = $Script:tf_scripts_bucket.replace("{bucket}", $GcsPrefix)
110 |
111 | Write-Log "Looking for scripts in $script_bucket in initial-alwayon-startup-script"
112 |
113 | ## Main
114 | # Instance specific variables
115 | $script_name = 'sql_install.ps1'
116 | $script_subpath = 'sqlserver'
117 | $task_name = "SQLInstall"
118 |
119 | # Create the C:\C2D folder
120 | if (!(Test-path -path $Script:install_path )) {
121 | try {
122 | New-Item -ItemType directory -Path $Script:install_path
123 | }
124 | catch {
125 | _PrintError
126 | exit 1
127 | }
128 | }
129 |
130 | # Download the scripts
131 | # Base Script
132 | $base_script_path = "$Script:c2d_scripts_bucket/c2d_base.psm1"
133 | $base_script = "$Script:install_path\c2d_base.psm1"
134 | if (DownloadScript -path $base_script_path -filename $base_script) {
135 | Write-Log "File downloaded successfully."
136 | }
137 | else {
138 | Write-Log "File not found."
139 | exit 2
140 | }
141 |
142 | # Copy Run Script down
143 | $run_script = "$Script:install_path\$script_name"
144 | $run_script_path = "$script_bucket/$script_name"
145 | Write-Log "run_script_path is $run_script_path"
146 | gsutil cp $run_script_path $run_script 2>&1 | %{ "$_" }
147 | Write-Log "Scripts copied down"
148 |
149 | # Execute the script
150 | Write-Log "Checking if $task_name sctask exists?"
151 | $sc_task = Get-ScheduledTask -TaskName $task_name -ErrorAction SilentlyContinue
152 | if ($sc_task) {
153 | Write-Log "$task_name schtask exists."
154 | try {
155 | Write-Log "-- Executing sctask $task_name. --"
156 | $response = Start-ScheduledTask -TaskName $task_name
157 | Write-Log $response
158 |
159 | }
160 | catch {
161 | $type = $_.Exception.GetType().FullName
162 | $message = $_.Exception.Message
163 | Write-Log "$type $message"
164 | exit 1
165 | }
166 | }
167 | else {
168 | Write-Log "schtask $task_name does not exists."
169 | Write-Log "Executing: $run_script"
170 | try {
171 | & $run_script -task_name $task_name
172 | }
173 | catch {
174 | $type = $_.Exception.GetType().FullName
175 | $message = $_.Exception.Message
176 | Write-Log "$type $message"
177 | exit 1
178 | }
179 | }
--------------------------------------------------------------------------------
/powershell/bootstrap/install-sql-server-principal-step-1.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Function Set-RuntimeConfigVariable {
18 | Param(
19 | [Parameter(Mandatory=$True)][String] $ConfigPath,
20 | [Parameter(Mandatory=$True)][String] $Variable,
21 | [Parameter(Mandatory=$True)][String] $Text
22 | )
23 |
24 | $Auth = $(gcloud auth print-access-token)
25 |
26 | $Path = "$ConfigPath/variables"
27 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$Path"
28 |
29 | $Json = (@{
30 | name = "$Path/$Variable"
31 | text = $Text
32 | } | ConvertTo-Json)
33 |
34 | $Headers = @{
35 | Authorization = "Bearer " + $Auth
36 | }
37 |
38 | $Params = @{
39 | Method = "POST"
40 | Headers = $Headers
41 | ContentType = "application/json"
42 | Uri = $Url
43 | Body = $Json
44 | }
45 |
46 | Try {
47 | Return Invoke-RestMethod @Params
48 | }
49 | Catch {
50 | $Reader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
51 | $ErrResp = $Reader.ReadToEnd() | ConvertFrom-Json
52 | $Reader.Close()
53 | Return $ErrResp
54 | }
55 |
56 | }
57 |
58 | Function Get-RuntimeConfigWaiter {
59 | Param(
60 | [Parameter(Mandatory=$True)][String] $ConfigPath,
61 | [Parameter(Mandatory=$True)][String] $Waiter
62 | )
63 |
64 | $Auth = $(gcloud auth print-access-token)
65 |
66 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters/$Waiter"
67 | $Headers = @{
68 | Authorization = "Bearer " + $Auth
69 | }
70 | $Params = @{
71 | Method = "GET"
72 | Headers = $Headers
73 | Uri = $Url
74 | }
75 |
76 | Return Invoke-RestMethod @Params
77 | }
78 |
79 | Function Wait-RuntimeConfigWaiter {
80 | Param(
81 | [Parameter(Mandatory=$True)][String] $ConfigPath,
82 | [Parameter(Mandatory=$True)][String] $Waiter,
83 | [int] $Sleep = 60
84 | )
85 | $RuntimeWaiter = $Null
86 | While (($RuntimeWaiter -eq $Null) -Or (-Not $RuntimeWaiter.done)) {
87 | $RuntimeWaiter = Get-RuntimeConfigWaiter -ConfigPath $ConfigPath -Waiter $Waiter
88 | If (-Not $RuntimeWaiter.done) {
89 | Write-Host "Waiting for [$ConfigPath/waiters/$Waiter]..."
90 | Sleep $Sleep
91 | }
92 | }
93 | Return $RuntimeWaiter
94 | }
95 |
96 |
97 | Function Get-GoogleMetadata() {
98 | Param (
99 | [Parameter(Mandatory=$True)][String] $Path
100 | )
101 | Try {
102 | Return Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/$Path
103 | }
104 | Catch {
105 | Return $Null
106 | }
107 | }
108 |
109 |
110 | Write-Host "Prepare principal SQL Server script started..."
111 |
112 | $name = Get-GoogleMetadata "instance/name"
113 | $zone = Get-GoogleMetadata "instance/zone"
114 |
115 | If ("true" -like (Get-GoogleMetadata "instance/attributes/remove-address")) {
116 | Write-Host "Removing external address..."
117 | gcloud compute instances delete-access-config $name --zone $zone
118 | }
119 |
120 |
121 | Write-Output "Fetching metadata parameters..."
122 |
123 | $DomainControllerAddress = Get-GoogleMetadata "instance/attributes/domain-controller-address"
124 | $Domain = Get-GoogleMetadata "instance/attributes/domain-name"
125 | $NetBiosName = Get-GoogleMetadata "/instance/attributes/netbios-name"
126 | $KmsKey = Get-GoogleMetadata "instance/attributes/kms-key"
127 | $GcsPrefix = Get-GoogleMetadata "instance/attributes/gcs-prefix"
128 | $RuntimeConfig = Get-GoogleMetadata "instance/attributes/runtime-config"
129 | $SuccessPath = Get-GoogleMetadata "instance/attributes/wait-on"
130 | $ProjectId = Get-GoogleMetadata "instance/attributes/project-id"
131 | $FullRTConfigPath = "projects/$ProjectId/configs/$RuntimeConfig"
132 |
133 | # the path to the runtime-config must be the full path ie.
134 | # "projects/{project-name}/configs/acme-runtime-config"
135 | # the success path is what is in wait-on
136 | # waiter name does not need to be passed in
137 | $Waiter = $name + '_waiter'
138 |
139 | ## remaining script has external dependencies, so invoke waiter before continuing
140 | Write-Host "Waiting on $Waiter"
141 | Wait-RuntimeConfigWaiter -ConfigPath $FullRTConfigPath -Waiter $Waiter
142 | Write-Host "Waiting completed ... $Waiter"
143 | Write-Host "Configuring network..."
144 |
145 |
146 | # This is the new part all credit due to click to deploy team
147 |
148 | Set-StrictMode -Version Latest
149 |
150 | $script:gce_install_dir = 'C:\Program Files\Google\Compute Engine\sysprep'
151 |
152 | # Import Modules
153 | try {
154 | Import-Module $script:gce_install_dir\gce_base.psm1 -ErrorAction Stop
155 | }
156 | catch [System.Management.Automation.ActionPreferenceStopException] {
157 | Write-Host $_.Exception.GetBaseException().Message
158 | Write-Host ("Unable to import GCE module from $script:gce_install_dir. " +
159 | 'Check error message, or ensure module is present.')
160 | exit 2
161 | }
162 |
163 | # Default Values
164 | $Script:c2d_scripts_bucket = 'c2d-windows/scripts'
165 | #$Script:tf_scripts_bucket = 'gs://acme-deployment/powershell/bootstrap'
166 | $Script:tf_scripts_bucket = '{bucket}/powershell/bootstrap'
167 | $Script:install_path="C:\C2D" # Folder for downloads
168 | $script:show_msgs = $false
169 | $script:write_to_serial = $false
170 |
171 |
172 | # Functions
173 | function DownloadScript {
174 | <#
175 | .SYNOPSIS
176 | Downloads a script to the localmachine from GCS.
177 | .DESCRIPTION
178 | Uses WebClient to download a script file.
179 | .EXAMPLE
180 | DownloadScript -path bucket/.. -filename
181 | #>
182 | param (
183 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
184 | $path,
185 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
186 | $filename,
187 | [Switch] $overwrite
188 | )
189 | $storage_url = 'http://storage.googleapis.com'
190 | $download_url = "$storage_url/$path"
191 |
192 |
193 | # Check if file already exists and act accordingly.
194 | if ((Test-path -path $filename)){
195 | if ($overwrite){
196 | Write-Log "$filename already exists. Overwrite flag set."
197 | _DeleteFiles -files $filename
198 | }
199 | else {
200 | Write-Log "$filename already exists. Overwrite flag notset."
201 | return $true
202 | }
203 | }
204 | # Download the file
205 | Write-Log "Original download url: $download_url"
206 | # To avoid cache issues
207 | $url = $download_url + "?random=" + (Get-Random).ToString()
208 |
209 | Write-Log "Downloading $url to $filename"
210 | try {
211 | Invoke-WebRequest -Uri $url -OutFile $filename -Headers @{"Cache-Control"="private"}
212 | }
213 | catch [System.Net.WebException] {
214 | $response = $_.Exception.Response
215 | if ($response) {
216 | _PrintError
217 | Write-Log $response.StatusCode -error # This is a System.Net.HttpStatusCode enum value
218 | Write-Log $response.StatusCode.value__ -error # This is the numeric version.
219 | }
220 | else {
221 | $type = $_.Exception.GetType().FullName
222 | $message = $_.Exception.Message
223 | Write-Log "$type $message"
224 | }
225 | return $false
226 | }
227 |
228 | # Check if download successfull
229 | if ((Test-path -path $filename)){
230 | return $true
231 | }
232 | else {
233 | Write-Log "File not found."
234 | return $false
235 | }
236 | }
237 |
238 |
239 | ## Main
240 | # Instance specific variables
241 | $script_name = 'sql_install.ps1'
242 | $script_subpath = 'sqlserver'
243 | $task_name = "SQLInstall"
244 |
245 | # Create the C:\C2D folder
246 | if (!(Test-path -path $Script:install_path )) {
247 | try {
248 | New-Item -ItemType directory -Path $Script:install_path
249 | }
250 | catch {
251 | _PrintError
252 | exit 1
253 | }
254 | }
255 |
256 | # Download the scripts
257 | # Base Script
258 | $base_script_path = "$Script:c2d_scripts_bucket/c2d_base.psm1"
259 | $base_script = "$Script:install_path\c2d_base.psm1"
260 | if (DownloadScript -path $base_script_path -filename $base_script) {
261 | Write-Log "File downloaded successfully."
262 | }
263 | else {
264 | Write-Log "File not found."
265 | exit 2
266 | }
267 |
268 |
269 | # Copy Run Script down from our bucket (Not c2d)
270 | $GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
271 | $script_bucket = $Script:tf_scripts_bucket.replace("{bucket}", $GcsPrefix)
272 |
273 | Write-Log "Looking for scripts in $script_bucket in initial-sql-server-principal.ps1"
274 |
275 | $run_script = "$Script:install_path\$script_name"
276 | $run_script_path = "$script_bucket/$script_name"
277 | gsutil cp $run_script_path $run_script 2>&1 | %{ "$_" }
278 |
279 |
280 | # Execute the script
281 | Write-Log "Checking if $task_name sctask exists?"
282 | $sc_task = Get-ScheduledTask -TaskName $task_name -ErrorAction SilentlyContinue
283 | if ($sc_task) {
284 | Write-Log "$task_name schtask exists."
285 | try {
286 | Write-Log "-- Executing sctask $task_name. --"
287 | $response = Start-ScheduledTask -TaskName $task_name
288 | Write-Log $response
289 |
290 | }
291 | catch {
292 | $type = $_.Exception.GetType().FullName
293 | $message = $_.Exception.Message
294 | Write-Log "$type $message"
295 | exit 1
296 | }
297 | }
298 | else {
299 | Write-Log "schtask $task_name does not exists."
300 | Write-Log "Executing: $run_script"
301 | try {
302 | & $run_script -task_name $task_name
303 | }
304 | catch {
305 | $type = $_.Exception.GetType().FullName
306 | $message = $_.Exception.Message
307 | Write-Log "$type $message"
308 | exit 1
309 | }
310 | }
311 |
312 | Write-Host "SQL script completed. Removing from metadata..."
313 | # remove startup script from metadata to prevent rerun on reboot
314 | $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
315 | $zone = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/zone
316 | gcloud compute instances remove-metadata "$name" --zone $zone --keys windows-startup-script-url
317 |
318 | Write-Host "Signaling completion..."
319 |
--------------------------------------------------------------------------------
/powershell/bootstrap/primary-domain-controller-step-1.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Function New-RandomString {
18 | Param(
19 | [int] $Length = 10,
20 | [char[]] $AllowedChars = $Null
21 | )
22 | If ($AllowedChars -eq $Null) {
23 | (,(33,126)) | % { For ($a=$_[0]; $a -le $_[1]; $a++) { $AllowedChars += ,[char][byte]$a } }
24 | }
25 | For ($i=1; $i -le $Length; $i++) {
26 | $Temp += ( $AllowedChars | Get-Random )
27 | }
28 | Return $Temp
29 | }
30 | Function New-RandomPassword() {
31 | Param(
32 | [int] $Length = 16,
33 | [char[]] $AllowedChars = $Null
34 | )
35 | Return New-RandomString -Length $Length -AllowedChars $AllowedChars | ConvertTo-SecureString -AsPlainText -Force
36 | }
37 | Function Unwrap-SecureString() {
38 | Param(
39 | [System.Security.SecureString] $SecureString
40 | )
41 | Return (New-Object -TypeName System.Net.NetworkCredential -ArgumentList '', $SecureString).Password
42 | }
43 |
44 | Function Get-GoogleMetadata() {
45 | Param (
46 | [Parameter(Mandatory=$True)][String] $Path
47 | )
48 | Try {
49 | Return Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/$Path
50 | }
51 | Catch {
52 | Return $Null
53 | }
54 | }
55 |
56 |
57 | Write-Host "Bootstrap script started..."
58 |
59 |
60 | #Write-Host "Installing AD features in background..."
61 | #Start-Job -ScriptBlock { Install-WindowsFeature -name AD-Domain-Services -IncludeManagementTools }
62 | Write-Host "Installing AD features..."
63 | Install-WindowsFeature -name AD-Domain-Services -IncludeManagementTools
64 |
65 |
66 | #Write-Host "Removing external address in background..."
67 | #Start-Job -ScriptBlock {
68 | # # windows should have activated before script is invoked, so now remove external address
69 | # $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
70 | # $zone = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/zone
71 | # gcloud compute instances delete-access-config $name --zone $zone
72 | #}
73 |
74 | If ("true" -like (Get-GoogleMetadata "instance/attributes/remove-address")) {
75 | Write-Host "Removing external address..."
76 | $name = Get-GoogleMetadata "instance/name"
77 | $zone = Get-GoogleMetadata "instance/zone"
78 | gcloud compute instances delete-access-config $name --zone $zone
79 | }
80 |
81 |
82 | Write-Host "Configuring network..."
83 | # reconfigure dhcp address as static to avoid warnings during dcpromo
84 | $IpAddr = Get-NetIPAddress -InterfaceAlias Ethernet -AddressFamily IPv4
85 | $IpConf = Get-NetIPConfiguration -InterfaceAlias Ethernet
86 | Set-NetIPInterface `
87 | -InterfaceAlias Ethernet `
88 | -Dhcp Disabled
89 | New-NetIPAddress `
90 | -InterfaceAlias Ethernet `
91 | -IPAddress $IpAddr.IPAddress `
92 | -AddressFamily IPv4 `
93 | -PrefixLength $IpAddr.PrefixLength `
94 | -DefaultGateway $IpConf.IPv4DefaultGateway.NextHop
95 |
96 | # set dns to google cloud default, will be set to loopback once dns feature is installed
97 | Set-DnsClientServerAddress -InterfaceAlias Ethernet -ServerAddresses $IpConf.IPv4DefaultGateway.NextHop
98 |
99 | # above can cause network blip, so wait until metadata server is responsive
100 | $HaveMetadata = $False
101 | While( ! $HaveMetadata ) { Try {
102 | Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/ 1>$Null 2>&1
103 | $HaveMetadata = $True
104 | } Catch {
105 | Write-Host "Waiting on metadata..."
106 | Start-Sleep 5
107 | } }
108 | Write-Host "Contacted metadata server. Proceeding..."
109 |
110 |
111 | Write-Host "Fetching metadata parameters..."
112 | $Domain = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/domain-name
113 | $NetBiosName = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/netbios-name
114 | $KmsKey = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/kms-key
115 | $GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
116 | $Region = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/region
117 | $KmsRegion = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/keyring-region
118 | $Keyring = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/keyring
119 | #$RuntimeConfig = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/runtime-config
120 |
121 | Write-Host "KMS Key has been fetched"
122 |
123 | Write-Host "Configuring admin credentials..."
124 | $SafeModeAdminPassword = New-RandomPassword
125 | $LocalAdminPassword = New-RandomPassword
126 |
127 | Set-LocalUser Administrator -Password $LocalAdminPassword
128 | Enable-LocalUser Administrator
129 |
130 | Write-Host "Saving encrypted credentials in GCS..."
131 | If ($GcsPrefix.EndsWith("/")) {
132 | $GcsPrefix = $GcsPrefix -Replace ".$"
133 | }
134 | $TempFile = New-TemporaryFile
135 |
136 | Unwrap-SecureString $LocalAdminPassword | gcloud kms encrypt --key $KmsKey --plaintext-file - --ciphertext-file $TempFile.FullName --location $KmsRegion --keyring $Keyring
137 | gsutil cp $TempFile.FullName "$GcsPrefix/output/domain-admin-password.bin"
138 |
139 | Unwrap-SecureString $SafeModeAdminPassword | gcloud kms encrypt --key $KmsKey --plaintext-file - --ciphertext-file $TempFile.FullName --location $KmsRegion --keyring $Keyring
140 | gsutil cp $TempFile.FullName "$GcsPrefix/output/dsrm-admin-password.bin"
141 |
142 | Remove-Item $TempFile.FullName -Force
143 |
144 | Write-Host "Waiting for background jobs..."
145 | Get-Job | Wait-Job
146 |
147 |
148 | Write-Host "Creating AD forest..."
149 |
150 | $Params = @{
151 | DomainName = $Domain
152 | DomainNetbiosName = $NetBiosName
153 | InstallDNS = $True
154 | NoRebootOnCompletion = $True
155 | SafeModeAdministratorPassword = $SafeModeAdminPassword
156 | Force = $True
157 | }
158 | Install-ADDSForest @Params
159 |
160 |
161 | Write-Host "Configuring startup metadata..."
162 | $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
163 | $zone = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/zone
164 | gcloud compute instances add-metadata "$name" --zone $zone --metadata windows-startup-script-url="$GcsPrefix/powershell/bootstrap/primary-domain-controller-step-2.ps1"
165 |
166 |
167 | Write-Host "Restarting computer after step 1 ..."
168 |
169 | Restart-Computer
170 |
--------------------------------------------------------------------------------
/powershell/bootstrap/primary-domain-controller-step-2.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Function Set-RuntimeConfigVariable {
18 | Param(
19 | [Parameter(Mandatory=$True)][String] $ConfigPath,
20 | [Parameter(Mandatory=$True)][String] $Variable,
21 | [Parameter(Mandatory=$True)][String] $Text
22 | )
23 |
24 | $Auth = $(gcloud auth print-access-token)
25 |
26 | $Path = "$ConfigPath/variables"
27 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$Path"
28 |
29 | $Json = (@{
30 | name = "$Path/$Variable"
31 | text = $Text
32 | } | ConvertTo-Json)
33 |
34 | $Headers = @{
35 | Authorization = "Bearer " + $Auth
36 | }
37 |
38 | $Params = @{
39 | Method = "POST"
40 | Headers = $Headers
41 | ContentType = "application/json"
42 | Uri = $Url
43 | Body = $Json
44 | }
45 |
46 | Try {
47 | Return Invoke-RestMethod @Params
48 | }
49 | Catch {
50 | $Reader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
51 | $ErrResp = $Reader.ReadToEnd() | ConvertFrom-Json
52 | $Reader.Close()
53 | Return $ErrResp
54 | }
55 |
56 | }
57 |
58 | Function Get-RuntimeConfigWaiter {
59 | Param(
60 | [Parameter(Mandatory=$True)][String] $ConfigPath,
61 | [Parameter(Mandatory=$True)][String] $Waiter
62 | )
63 |
64 | $Auth = $(gcloud auth print-access-token)
65 |
66 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters/$Waiter"
67 | $Headers = @{
68 | Authorization = "Bearer " + $Auth
69 | }
70 | $Params = @{
71 | Method = "GET"
72 | Headers = $Headers
73 | Uri = $Url
74 | }
75 |
76 | Return Invoke-RestMethod @Params
77 | }
78 |
79 | Function Wait-RuntimeConfigWaiter {
80 | Param(
81 | [Parameter(Mandatory=$True)][String] $ConfigPath,
82 | [Parameter(Mandatory=$True)][String] $Waiter,
83 | [int] $Sleep = 60
84 | )
85 | $RuntimeWaiter = $Null
86 | While (($RuntimeWaiter -eq $Null) -Or (-Not $RuntimeWaiter.done)) {
87 | $RuntimeWaiter = Get-RuntimeConfigWaiter -ConfigPath $ConfigPath -Waiter $Waiter
88 | If (-Not $RuntimeWaiter.done) {
89 | Write-Host "Waiting for [$ConfigPath/waiters/$Waiter]..."
90 | Sleep $Sleep
91 | }
92 | }
93 | Return $RuntimeWaiter
94 | }
95 |
96 |
97 | Write-Host "Runtime-config script started..."
98 |
99 |
100 | Write-Host "Configuring NTP..."
101 | # use google internal time server
102 | w32tm /config /manualpeerlist:"metadata.google.internal" /syncfromflags:manual /reliable:yes /update
103 |
104 |
105 | ## download and run user creation script
106 | #$GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
107 | #$TempFile = New-TemporaryFile
108 | #$TempFile.MoveTo($TempFile.fullName + ".ps1")
109 | #gsutil cp $GcsPrefix/bootstrap/create-domain-users.ps1 $TempFile.FullName#Invoke-Expression $TempFile.FullName
110 | #Remove-Item $TempFile.FullName -Force
111 |
112 |
113 | # poll domain controller until it appears ready
114 | Do {
115 | Try {
116 | $test = Get-ADDomain
117 | }
118 | Catch {
119 | Write-Host "Waiting for DC to become available..."
120 | Sleep 15
121 | }
122 | }
123 | Until ($test)
124 |
125 |
126 | Write-Host "Configuring startup metadata..."
127 | # remove startup script from metadata to prevent rerun on reboot
128 | $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
129 | $zone = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/zone
130 | gcloud compute instances remove-metadata "$name" --zone $zone --keys windows-startup-script-url
131 |
132 | Write-Host "Signaling completion..."
133 |
134 | # flag completion of bootstrap requires beta gcloud component
135 | $projectId = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/project-id
136 | $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
137 | $RuntimeConfig = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/runtime-config
138 | $deploymentName = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/deployment-name
139 | $statusPath = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/status-variable-path
140 |
141 | Write-Host "Config is at: projects/$projectId/configs/$RuntimeConfig"
142 | Write-Host "Variable is: bootstrap/$deploymentName/$statusPath/success/time"
143 |
144 | Set-RuntimeConfigVariable -ConfigPath "projects/$projectId/configs/$RuntimeConfig" -Variable bootstrap/$deploymentName/$statusPath/success/time -Text (Get-Date -Format g)
145 |
146 | Write-Host "Step 2 completed"
147 |
--------------------------------------------------------------------------------
/powershell/c2d/c2d_base.psm1:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | <#
16 | .SYNOPSIS
17 | C2D Base Modules.
18 | .DESCRIPTION
19 | Base modules needed for C2D Powershell scripts to run scripts to run.
20 | .NOTES
21 | LastModifiedDate: $Date: 2017/02/26 $
22 | Version: $Revision: #2 $
23 |
24 | #requires -version 3.0
25 | #>
26 |
27 |
28 | $Script:gce_install_dir = 'C:\Program Files\Google\Compute Engine\sysprep'
29 | $Script:run_time_base = 'https://runtimeconfig.googleapis.com/v1beta1'
30 |
31 | # Import Modules
32 | try {
33 | Import-Module $script:gce_install_dir\gce_base.psm1 -ErrorAction Stop
34 | }
35 | catch [System.Management.Automation.ActionPreferenceStopException] {
36 | Write-Host $_.Exception.GetBaseException().Message
37 | Write-Host ("Unable to import GCE module from $script:gce_install_dir. " +
38 | 'Check error message, or ensure module is present.')
39 | exit 2
40 | }
41 |
42 |
43 | # Functions
44 | function _CreateRunTimeConfig {
45 | <#
46 | .SYNOPSIS
47 | Create a new run-time config.
48 | .DESCRIPTION
49 | Generate new run-time config for a given instance.
50 | This will be separate from the deployment manager instance config.
51 | .EXAMPLE
52 | _CreateRunTimeConfig
53 | #>
54 | param (
55 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
56 | $config_name
57 | )
58 |
59 | $project_id = _FetchFromMetaData -property 'project-id' -project_only
60 | $run_time_path = "projects/$project_id/configs"
61 | $newConfig = @{
62 | name = "$run_time_path/$config_name"
63 | }
64 |
65 | $json = $newConfig | ConvertTo-Json
66 | Write-Log "Writing a new startup watcher config: $json"
67 |
68 | # TODO: Need to add a check for exisiting path.
69 | if (_RunTimePost -path "$run_time_path/" -post $json) {
70 | return "$run_time_path/$config_name"
71 | }
72 | else {
73 | Write-Log "Failed to create ConfigName: $config_name" -error
74 | }
75 | }
76 |
77 | function _GetRuntimeConfig {
78 | <#
79 | .SYNOPSIS
80 | Fetched run-time config.
81 | .DESCRIPTION
82 | Get the URL for runtime config from the metadataserver
83 | .EXAMPLE
84 | _GetRuntimeConfig
85 | #>
86 |
87 | $config_name = $null
88 | # Get RuntimeConfig URL for the deployment
89 | $runtime_config = _FetchFromMetaData -property 'attributes/status-config-url'
90 |
91 | if ($runtime_config) {
92 | # Use second part of the config URL
93 | $config_name = (($runtime_config -split "$Script:run_time_base/")[1])
94 | return $config_name
95 | }
96 | else {
97 | Write-Log 'No RunTimeConfig found URL found in metadata.' -error
98 | return $false
99 | }
100 | }
101 |
102 | function _RunTimeQuery{
103 | <#
104 | .SYNOPSIS
105 | Do a POST/GET request
106 | .DESCRIPTION
107 | Is a sub function called to do POST request
108 | .EXAMPLE
109 | _RunTimeQuery -get -path
110 | .EXAMPLE
111 | _RunTimeQuery -post -path -body
112 | #>
113 | param (
114 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
115 | $access_token,
116 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
117 | $path,
118 | [Alias('get')]
119 | [Switch] $get_req,
120 | [Alias('post')]
121 | [Switch] $post_req,
122 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
123 | $body
124 | )
125 |
126 | # Define URL
127 | $url = "$Script:run_time_base/$path"
128 | $header = @{"Authorization"="Bearer $access_token"}
129 |
130 | if ($get_req) {
131 | try {
132 | # GET request against a URL
133 | $response = Invoke-RestMethod -Uri $url -Method GET -Headers $header `
134 | -ErrorAction SilentlyContinue
135 | return $response
136 | }
137 | catch [System.Net.WebException] {
138 | if ($_.Exception.Response) {
139 | if ($_.Exception.Response.StatusCode.value__ -eq 404){
140 | Write-Log "$url does not exist." -warning
141 | return $_.Exception.Response.StatusCode.value__
142 | }
143 | Write-Log $_.Exception.Response.StatusCode.value__ -error # This is the numeric version.
144 | Write-Log $_.Exception.Response.StatusCode -error # This is a System.Net.HttpStatusCode enum value
145 | }
146 | else {
147 | _PrintError
148 | }
149 | return $false
150 | }
151 | }
152 | elseif ($post_req){
153 | if(!$body){
154 | Write-Log "-body parameter is required with -post."
155 | return
156 | }
157 | $content_type = 'application/json'
158 | try {
159 | # POST request against a URL
160 | $response = Invoke-RestMethod -Uri $url -ContentType $content_type `
161 | -Method POST -Body $body -Headers $header `
162 | -ErrorAction SilentlyContinue
163 | return $response
164 | }
165 | catch [System.Net.WebException] {
166 | $response = $_.Exception.Response
167 | if ($response) {
168 | if ($response.StatusCode.value__ -eq 409){
169 | Write-Log "$path already exists." -warning
170 | return $response.StatusCode.value__
171 | }
172 | Write-Log "Failed to POST: $path"
173 | Write-Log $response.StatusCode -error # This is a System.Net.HttpStatusCode enum value
174 | Write-Log $response.StatusCode.value__ -error # This is the numeric version.
175 | }
176 | else {
177 | _PrintError
178 | }
179 | return $false
180 | }
181 | catch {
182 | _PrintError
183 | Write-Log $_.Exception.GetType().FullName -error
184 | Write-Log $_.Exception -error
185 | return $false
186 | }
187 | }
188 | }
189 |
190 | function CreateRunTimeVariable {
191 | <#
192 | .SYNOPSIS
193 | Create a new runtime variable during run time.
194 | .DESCRIPTION
195 | Generate new run time variable for the instance
196 | .EXAMPLE
197 | CreateRunTimeVariable -config_path -var_name
198 | #>
199 | param (
200 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
201 | $config_path,
202 | [Alias('random')]
203 | [Switch] $random_var,
204 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
205 | $var_name,
206 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
207 | $var_text,
208 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
209 | $var_value
210 | )
211 |
212 | # Get access Token to auth for RunTime
213 | try {
214 | $access_token = (_FetchFromMetaData -property `
215 | 'service-accounts/default/token'| `
216 | ConvertFrom-Json).access_token
217 | }
218 | catch {
219 | _PrintError
220 | Write-Log $_.Exception.GetType().FullName -error
221 | Write-Log 'Failed to get access token for Runtime Config' -error
222 | return $false
223 | }
224 |
225 | if ($random_var) {
226 | $rand_num = Get-Random
227 | $var_name += "/$rand_num"
228 | }
229 |
230 | Write-Log "Writing $var_name -> $config_path"
231 | # Generate the body to create the key variable
232 | $variable = @{
233 | name = "$config_path/variables/$var_name"
234 | }
235 |
236 | $var_json = $variable | ConvertTo-Json
237 |
238 | # POST the request
239 | $response = _RunTimeQuery -post -path "$config_path/variables" `
240 | -body $var_json -access_token $access_token
241 | if ($response) {
242 | Write-Log "Created: $config_path/variables/$var_name, with response: $response"
243 | return $response
244 | }
245 | else{
246 | Write-Log "Failed to create RunTimeConfig: $config_path/variables" -error
247 | return $false
248 | }
249 | }
250 |
251 | function CreateSCTask {
252 | <#
253 | .SYNOPSIS
254 | Create a Scheduled Task.
255 | .DESCRIPTION
256 | Generate new scheduledTask Action
257 | .EXAMPLE
258 | CreateSCTask -task_name
259 | #>
260 | param (
261 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
262 | $name,
263 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
264 | $user,
265 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
266 | $password,
267 | [Alias('file')]
268 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
269 | $sch_file,
270 | [Alias('trigger')]
271 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
272 | $when_to_trigger
273 | )
274 |
275 | $trigger = $null
276 | Write-Log "Creating task $name"
277 |
278 | # Define trigger
279 | if ($when_to_trigger){
280 | $trigger = $when_to_trigger
281 | }
282 | else {
283 | $trigger = New-ScheduledTaskTrigger -AtStartup
284 | }
285 |
286 | $action = New-ScheduledTaskAction -Execute "powershell.exe" -argument `
287 | "-ExecutionPolicy Bypass -NonInteractive -NoProfile -File $sch_file -AsJob"
288 | $setting = New-ScheduledTaskSettingsSet
289 | try {
290 | Write-Log "Adding task: $name, with user: $script:domain_service_account."
291 | Register-ScheduledTask $name -Action $action -Trigger $trigger -Settings $setting `
292 | -User $user -Password $password -RunLevel Highest
293 | }
294 | catch {
295 | _PrintError
296 | }
297 | }
298 |
299 | function DeleteSCTask {
300 | <#
301 | .SYNOPSIS
302 | Deletes a Scheduled Task.
303 | .DESCRIPTION
304 | Deletes a scheduledTask Action
305 | .EXAMPLE
306 | DeleteSCTask -task_name
307 | #>
308 | param (
309 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
310 | $name
311 | )
312 | Write-Log "Unregistering $name"
313 | try {
314 | Unregister-ScheduledTask -TaskName $name -Confirm:$false
315 | }
316 | catch {
317 | _PrintError
318 | }
319 | }
320 |
321 | function WaitForRuntime {
322 | <#
323 | .SYNOPSIS
324 | Waits for a runtime variable
325 | .DESCRIPTION
326 | Waits for a runtime variable before giving up
327 | .EXAMPLE
328 | WaitForRuntime -path $path -timeout
329 | #>
330 | param (
331 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
332 | $path,
333 | [Alias('timeout')]
334 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
335 | $wait_time_min
336 | )
337 | # Get RuntimeConfig URL for the deployment
338 | $access_token = (_FetchFromMetaData -property `
339 | 'service-accounts/default/token'| `
340 | ConvertFrom-Json).access_token
341 | $runtime_config = _GetRuntimeConfig
342 | $query_path = "$runtime_config/variables/$path"
343 |
344 | Write-Logger "Querying path $query_path"
345 | if ($wait_time_min) {
346 | for ($i=0; $i -le $wait_time_min; $i++) {
347 | $response = _RunTimeQuery -get -path $query_path -access_token $access_token
348 |
349 | if($response) {
350 | if ($response -eq 404) {
351 | Write-Logger "$query_path not available. Will retry after 60 seconds.."
352 | Start-Sleep -s 60
353 | }
354 | else {
355 | Write-Logger "$query_path is available."
356 | return $response
357 | }
358 | }
359 | else {
360 | Write-Logger "Something went wrong"
361 | return $false
362 | }
363 | }
364 | }
365 | else {
366 | return _RunTimeQuery -get -path $query_path -access_token $access_token
367 | }
368 | }
369 |
370 | function Write-Logger {
371 | param (
372 | [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
373 | [AllowEmptyString()]
374 | [string]$data,
375 | [string]$port = 'COM1'
376 | )
377 |
378 | $timestamp = $(Get-Date)
379 | $timestampped_msg = "$timestamp $data"
380 | try {
381 | # define a new object to read serial ports
382 | $serial_port = New-Object System.IO.Ports.SerialPort $port, 9600, None, 8, One
383 | $serial_port.Open()
384 | # Write to the serial port
385 | $serial_port.WriteLine($timestampped_msg)
386 | Write-Host $timestampped_msg
387 | }
388 | catch {
389 | Write-Host 'Error writing to serial port'
390 | continue
391 | }
392 | finally {
393 | if ($serial_port) {
394 | $serial_port.Close()
395 | }
396 | }
397 | }
398 |
399 | function Write-ToReg {
400 | <#
401 | .SYNOPSIS
402 | Write To registry
403 | .DESCRIPTION
404 | Write a key to regisry
405 | .EXAMPLE
406 | Write-ToReg
407 | #>
408 | param (
409 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
410 | $path
411 | )
412 | # Create registry key
413 | try {
414 | $result = New-Item -Path $path -Force
415 | return $true
416 | }
417 | catch [System.IO.IOException] {
418 | Write-Log "$_.Exception.Message"
419 | Write-Log $result
420 | return $false
421 | }
422 | }
423 |
424 | function UpdateRunTimeWaiter {
425 | <#
426 | .SYNOPSIS
427 | Updtes a RunTimeWaiter.
428 | .DESCRIPTION
429 | Update RunTimeWaiter POST to a given path. By default writes to success
430 | .EXAMPLE
431 | UpdateRunTimeWaiter
432 | .EXAMPLE
433 | UpdateRunTimeWaiter -failure
434 | #>
435 | param (
436 | [Switch] $failure,
437 | [Alias('path')]
438 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
439 | $status_var_path
440 | )
441 |
442 | $key_path = $null
443 | $config_name = _GetRuntimeConfig
444 |
445 | if(!$config_name){
446 | Write-Log "Could not find runtime_config from metadata server" -error
447 | return $false
448 | }
449 |
450 | if (!$status_var_path) {
451 | $status_var_path = _FetchFromMetaData -property `
452 | 'attributes/status-variable-path'
453 | }
454 | else {
455 | Write-Log "Writing to custom subpath: $status_var_path"
456 | }
457 |
458 | if ($failure) {
459 | $key_path = "$status_var_path/failure"
460 | }
461 | else {
462 | $key_path = "$status_var_path/success"
463 | }
464 |
465 | $response = CreateRunTimeVariable -config_path $config_name -var_name $key_path -random
466 | }
467 |
468 | # Export all modules.
469 | Export-ModuleMember -Function * -Alias *
470 |
471 | # Clear out any existing errors.
472 | $error.Clear() | Out-Null
--------------------------------------------------------------------------------
/powershell/c2d/gce_base.psm1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | <#
18 | .SYNOPSIS
19 | GCE Base Modules.
20 | .DESCRIPTION
21 | Base modules needed for GCE Powershell scripts to run scripts to run.
22 |
23 | #requires -version 3.0
24 | #>
25 |
26 | # Default Values
27 | $global:write_to_serial = $false
28 | $global:metadata_server = 'metadata.google.internal'
29 | $global:hostname = [System.Net.Dns]::GetHostName()
30 | $global:log_file = $null
31 |
32 | # Functions
33 | function _AddToPath {
34 | <#
35 | .SYNOPSIS
36 | Adds GCE tool dir to SYSTEM PATH
37 | .DESCRIPTION
38 | This is a helper function which adds location to path
39 | #>
40 | param (
41 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
42 | [Alias('path')]
43 | $path_to_add
44 | )
45 |
46 | # Check if folder exists on the file system.
47 | if (!(Test-Path $path_to_add)) {
48 | Write-Log "$path_to_add does not exist, cannot be added to $env:PATH."
49 | return
50 | }
51 |
52 | try {
53 | $path_reg_key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
54 | $current_path = (Get-ItemProperty $path_reg_key).Path
55 | $check_path = ($current_path).split(';') | ? {$_ -like $path_to_add}
56 | }
57 | catch {
58 | Write-Log 'Could not read path from the registry.'
59 | _PrintError
60 | }
61 | # See if the folder is already in the path.
62 | if ($check_path) {
63 | Write-Log 'Folder already in system path.'
64 | }
65 | else {
66 | try {
67 | Write-Log "Adding $path_to_add to SYSTEM path."
68 | $new_path = $current_path + ';' + $path_to_add
69 | $env:Path = $new_path
70 | Set-ItemProperty $path_reg_key -name 'Path' -value $new_path
71 | }
72 | catch {
73 | Write-Log 'Failed to add to SYSTEM path.'
74 | _PrintError
75 | }
76 | }
77 | }
78 |
79 |
80 | function Clear-EventLogs {
81 | <#
82 | .SYNOPSIS
83 | Clear all eventlog enteries.
84 | .DESCRIPTION
85 | This uses the Get-Eventlog and Clear-EventLog powershell functions to
86 | clean the eventlogs for a machine.
87 | #>
88 |
89 | Write-Log 'Clearing events in EventViewer.'
90 | Get-WinEvent -ListLog * |
91 | Where-Object {($_.IsEnabled -eq 'True') -and ($_.RecordCount -gt 0)} |
92 | ForEach-Object {
93 | try{[System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($_.LogName)}catch{}
94 | }
95 | }
96 |
97 |
98 | function Clear-TempFolders {
99 | <#
100 | .SYNOPSIS
101 | Delete all files from temp folder location.
102 | .DESCRIPTION
103 | This function calls an array variable which contain location of all the
104 | temp files and folder which needs to be cleared out. We use the
105 | Remove-Item routine to delete the files in the temp directories.
106 | #>
107 |
108 | # Array of files and folder that need to be deleted.
109 | @("C:\Windows\Temp\*", "C:\Windows\Prefetch\*",
110 | "C:\Documents and Settings\*\Local Settings\temp\*\*",
111 | "C:\Users\*\Appdata\Local\Temp\*\*",
112 | "C:\Users\*\Appdata\Local\Microsoft\Internet Explorer\*",
113 | "C:\Users\*\Appdata\LocalLow\Temp\*\*",
114 | "C:\Users\*\Appdata\LocalLow\Microsoft\Internet Explorer\*") | ForEach-Object {
115 | if (Test-Path $_) {
116 | Remove-Item $_ -recurse -force -ErrorAction SilentlyContinue
117 | }
118 | }
119 | }
120 |
121 |
122 | function Get-MetaData {
123 | <#
124 | .SYNOPSIS
125 | Get attributes from GCE instances metadata.
126 | .DESCRIPTION
127 | Use Net.WebClient to fetch data from metadata server.
128 | .PARAMETER property
129 | Name of instance metadata property we want to fetch.
130 | .PARAMETER filename
131 | Name of file to save metadata contents to. If left out, returns contents.
132 | .EXAMPLE
133 | $hostname = _FetchFromMetaData -property 'hostname'
134 | Get-MetaData -property 'startup-script' -file 'script.bat'
135 | #>
136 | param (
137 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
138 | $property,
139 | $filename = $null,
140 | [switch] $project_only = $false,
141 | [switch] $instance_only = $false
142 | )
143 |
144 | $request_url = '/computeMetadata/v1/instance/'
145 | if ($project_only) {
146 | $request_url = '/computeMetadata/v1/project/'
147 | }
148 |
149 | $url = "http://$global:metadata_server$request_url$property"
150 |
151 | try {
152 | $client = _GetWebClient
153 | #Header
154 | $client.Headers.Add('Metadata-Flavor', 'Google')
155 | # Get Data
156 | if ($filename) {
157 | $client.DownloadFile($url, $filename)
158 | return
159 | }
160 | else {
161 | return ($client.DownloadString($url)).Trim()
162 | }
163 | }
164 | catch [System.Net.WebException] {
165 | if ($project_only -or $instance_only) {
166 | Write-Log "$property value is not set or metadata server is not reachable."
167 | }
168 | else {
169 | return (_FetchFromMetaData -project_only -property $property -filename $filename)
170 | }
171 | }
172 | catch {
173 | Write-Log "Unknown error in reading $url."
174 | _PrintError
175 | }
176 | }
177 |
178 |
179 | function _GenerateRandomPassword {
180 | <#
181 | .SYNOPSIS
182 | Generates random password which meet windows complexity requirements.
183 | .DESCRIPTION
184 | This function generates a password to be set on built-in account before
185 | it is disabled.
186 | .OUTPUTS
187 | Returns String
188 | .EXAMPLE
189 | _GeneratePassword
190 | #>
191 |
192 | # Define length of the password. Maximum and minimum.
193 | [int] $pass_min = 20
194 | [int] $pass_max = 35
195 | [string] $random_password = $null
196 |
197 | # Random password length should help prevent masking attacks.
198 | $password_length = Get-Random -Minimum $pass_min -Maximum $pass_max
199 |
200 | # Choose a set of ASCII characters we'll use to generate new passwords from.
201 | $ascii_char_set = $null
202 | for ($x=33; $x -le 126; $x++) {
203 | $ascii_char_set+=,[char][byte]$x
204 | }
205 |
206 | # Generate random set of characters.
207 | for ($loop=1; $loop -le $password_length; $loop++) {
208 | $random_password += ($ascii_char_set | Get-Random)
209 | }
210 | return $random_password
211 | }
212 |
213 |
214 | function _GetCOMPorts {
215 | <#
216 | .SYNOPSIS
217 | Get available serial ports. Check if a port exists, if yes returns $true
218 | .DESCRIPTION
219 | This function is used to check if a port exists on this machine.
220 | .PARAMETER $portname
221 | Name of the port you want to check if it exists.
222 | .OUTPUTS
223 | [boolean]
224 | .EXAMPLE
225 | _GetCOMPorts
226 | #>
227 |
228 | param (
229 | [parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
230 | [String]$portname
231 | )
232 |
233 | $exists = $false
234 | try {
235 | # Read available COM ports.
236 | $com_ports = [System.IO.Ports.SerialPort]::getportnames()
237 | if ($com_ports -match $portname) {
238 | $exists = $true
239 | }
240 | }
241 | catch {
242 | _PrintError
243 | }
244 | return $exists
245 | }
246 |
247 |
248 | function _GetWebClient {
249 | <#
250 | .SYNOPSIS
251 | Get Net.WebClient object.
252 | .DESCRIPTION
253 | Generata Webclient object for clients to use.
254 | .EXAMPLE
255 | $hostname = _GetWebClient
256 | #>
257 | $client = $null
258 | try {
259 | # WebClient to return.
260 | $client = New-Object Net.WebClient
261 | }
262 | catch [System.Net.WebException] {
263 | Write-Log 'Could not generate a WebClient object.'
264 | _PrintError
265 | }
266 | return $client
267 | }
268 |
269 |
270 | function _PrintError {
271 | <#
272 | .SYNOPSIS
273 | Prints Error Messages
274 | .DESCRIPTION
275 | This is a helper function which prints out error messages in catch
276 | .OUTPUTS
277 | Error message found during execution is printed out to the console.
278 | .EXAMPLE
279 | _PrintError
280 | #>
281 |
282 | # See all error objects.
283 | $error_obj = Get-Variable -Name Error -Scope 2 -ErrorAction SilentlyContinue
284 | if ($error_obj) {
285 | try {
286 | $message = $($error_obj.Value.Exception[0].Message)
287 | $line_no = $($error_obj.Value.InvocationInfo[0].ScriptLineNumber)
288 | $line_info = $($error_obj.Value.InvocationInfo[0].Line)
289 | $hresult = $($error_obj.Value.Exception[0].HResult)
290 | $calling_script = $($error_obj.Value.InvocationInfo[0].ScriptName)
291 |
292 | # Format error string
293 | if ($error_obj.Value.Exception[0].InnerException) {
294 | $inner_msg = $error_obj.Value.Exception[0].InnerException.Message
295 | $errmsg = "$inner_msg : $message {Line: $line_no : $line_info, HResult: $hresult, Script: $calling_script}"
296 | }
297 | else {
298 | $errmsg = "$message {Line: $line_no : $line_info, HResult: $hresult, Script: $calling_script}"
299 | }
300 | # Write message to output.
301 | Write-Log $errmsg -error
302 | }
303 | catch {
304 | Write-Log $_.Exception.GetBaseException().Message -error
305 | }
306 | }
307 |
308 | # Clear out the error.
309 | $error.Clear() | Out-Null
310 | }
311 |
312 |
313 | function Invoke-ExternalCommand {
314 | <#
315 | .SYNOPSIS
316 | Run External Command.
317 | .DESCRIPTION
318 | This function calls an external command outside of the powershell script and logs the output.
319 | .PARAMETER Executable
320 | Executable that needs to be run.
321 | .PARAMETER Arguments
322 | Arguments for the executable. Default is NULL.
323 | .EXAMPLE
324 | Invoke-ExternalCommand dir c:\
325 | #>
326 | [CmdletBinding(SupportsShouldProcess=$true)]
327 | param (
328 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
329 | [string]$Executable,
330 | [Parameter(ValueFromRemainingArguments=$true,
331 | ValueFromPipelineByPropertyName=$true)]
332 | $Arguments = $null
333 | )
334 | Write-Log "Running '$Executable' with arguments '$Arguments'"
335 | $out = &$Executable $Arguments 2>&1 | Out-String
336 | if ($out.Trim()) {
337 | $out.Trim().Split("`n") | ForEach-Object {
338 | Write-Log "--> $_"
339 | }
340 | }
341 | }
342 |
343 |
344 | function _TestAdmin {
345 | <#
346 | .SYNOPSIS
347 | Checks if the current Powershell instance is running with
348 | elevated privileges or not.
349 | .OUTPUTS
350 | System.Boolean
351 | True if the current Powershell is elevated, false if not.
352 | #>
353 | try {
354 | $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
355 | $principal = New-Object Security.Principal.WindowsPrincipal -ArgumentList $identity
356 | return $principal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator )
357 | }
358 | catch {
359 | Write-Log 'Failed to determine if the current user has elevated privileges.'
360 | _PrintError
361 | }
362 | }
363 |
364 |
365 | function _TestTCPPort {
366 | <#
367 | .SYNOPSIS
368 | Test TCP port on remote server
369 | .DESCRIPTION
370 | Use .Net Socket connection to connect to remote host and check if port is
371 | open.
372 | .PARAMETER remote_host
373 | Remote host you want to check TCP port for.
374 | .PARAMETER port_number
375 | TCP port number you want to check.
376 | .PARAMETER timeout
377 | Time you want to wait for.
378 | .RETURNS
379 | Return bool. $true if server is reachable at tcp port $false is not.
380 | .EXAMPLE
381 | _TestTCPPort -host 127.0.0.1 -port 80
382 | #>
383 | param (
384 | [Alias('host')]
385 | [string]$remote_host,
386 | [Alias('port')]
387 | [int]$port_number,
388 | [int]$timeout = 3000
389 | )
390 |
391 | $status = $false
392 | try {
393 | # Create a TCP Client.
394 | $socket = New-Object Net.Sockets.TcpClient
395 | # Use the TCP Client to connect to remote host port.
396 | $connection = $socket.BeginConnect($remote_host, $port_number, $null, $null)
397 | # Set the wait time
398 | $wait = $connection.AsyncWaitHandle.WaitOne($timeout, $false)
399 | if (!$wait) {
400 | # Connection failed, timeout reached.
401 | $socket.Close()
402 | }
403 | else {
404 | # Close the connection and report the error if there is one.
405 | $socket.EndConnect($connection) | Out-Null
406 | if (!$?) {
407 | Write-Log $error[0]
408 | }
409 | else {
410 | $status = $true
411 | }
412 | $socket.Close()
413 | }
414 | }
415 | catch {
416 | _PrintError
417 | }
418 | return $status
419 | }
420 |
421 |
422 | function Write-SerialPort {
423 | <#
424 | .SYNOPSIS
425 | Sending data to serial port.
426 | .DESCRIPTION
427 | Use this function to send data to serial port.
428 | .PARAMETER portname
429 | Name of port. The port to use (for example, COM1).
430 | .PARAMETER baud_rate
431 | The baud rate.
432 | .PARAMETER parity
433 | Specifies the parity bit for a SerialPort object.
434 | None: No parity check occurs (default).
435 | Odd: Sets the parity bit so that the count of bits set is an odd number.
436 | Even: Sets the parity bit so that the count of bits set is an even number.
437 | Mark: Leaves the parity bit set to 1.
438 | Space: Leaves the parity bit set to 0.
439 | .PARAMETER data_bits
440 | The data bits value.
441 | .PARAMETER stop_bits
442 | Specifies the number of stop bits used on the SerialPort object.
443 | None: No stop bits are used. This value is Currently not supported by the
444 | stop_bits.
445 | One: One stop bit is used (default).
446 | Two: Two stop bits are used.
447 | OnePointFive: 1.5 stop bits are used.
448 | .PARAMETER data
449 | Data to be sent to serial port.
450 | .PARAMETER wait_for_respond
451 | Wait for result of data sent.
452 | .PARAMETER close
453 | Remote close connection.
454 | .EXAMPLE
455 | Send data to serial port and exit.
456 | Write-SerialPort -portname COM1 -data 'Hello World'
457 | .EXAMPLE
458 | Send data to serial port and wait for respond.
459 | Write-SerialPort -portname COM1 -data 'dir C:\' -wait_for_respond
460 | #>
461 | [CmdletBinding(supportsshouldprocess=$true)]
462 | param (
463 | [parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
464 | [string]$portname,
465 | [Int]$baud_rate = 9600,
466 | [ValidateSet('None', 'Odd', 'Even', 'Mark', 'Space')]
467 | [string]$parity = 'None',
468 | [int]$data_bits = 8,
469 | [ValidateSet('None', 'One', 'Even', 'Two', 'OnePointFive')]
470 | [string]$stop_bits = 'One',
471 | [string]$data,
472 | [Switch]$wait_for_respond,
473 | [Switch]$close
474 | )
475 |
476 | if ($psCmdlet.shouldProcess($portname , 'Write data to local serial port')) {
477 | if ($close) {
478 | $data = 'close'
479 | $wait_for_respond = $false
480 | }
481 | try {
482 | # Define a new object to read serial ports.
483 | $port = New-Object System.IO.Ports.SerialPort $portname, $baud_rate, `
484 | $parity, $data_bits, $stop_bits
485 | $port.Open()
486 | # Write to the serial port.
487 | $port.WriteLine($data)
488 | # If wait_for_resond is specified.
489 | if ($wait_for_respond) {
490 | $result = $port.ReadLine()
491 | $result.Replace("#^#","`n")
492 | }
493 | $port.Close()
494 | }
495 | catch {
496 | _PrintError
497 | }
498 | }
499 | }
500 |
501 |
502 | function Write-Log {
503 | <#
504 | .SYNOPSIS
505 | Generate Log for the script.
506 | .DESCRIPTION
507 | Generate log messages, if COM1 port found write output to COM1 also.
508 | .PARAMETER $msg
509 | Message that needs to be logged
510 | .PARAMETER $is_important
511 | Surround the message with a line of hyphens.
512 | .PARAMETER $is_error
513 | Mark messages as Error in red text.
514 | .PARAMETER $is_warning
515 | Mark messages as Warning in yellow text.
516 | #>
517 | param (
518 | [parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
519 | [String]$msg,
520 | [Alias('important')]
521 | [Switch] $is_important,
522 | [Alias('error')]
523 | [Switch] $is_error,
524 | [Alias('warning')]
525 | [Switch] $is_warning
526 | )
527 | $timestamp = $(Get-Date -Format 'yyyy/MM/dd HH:mm:ss')
528 | if (-not ($global:logger)) {
529 | $global:logger = ''
530 | }
531 | try {
532 | # Add a boundary around an important message.
533 | if ($is_important) {
534 | $boundary = '-' * 60
535 | $timestampped_msg = @"
536 | ${timestamp} ${global:logger}: ${boundary}
537 | ${timestamp} ${global:logger}: ${msg}
538 | ${timestamp} ${global:logger}: ${boundary}
539 | "@
540 | }
541 | else {
542 | $timestampped_msg = "${timestamp} ${global:logger}: ${msg}"
543 | }
544 | # If a log file is set, use it.
545 | if ($global:log_file) {
546 | Add-Content $global:log_file "$timestampped_msg"
547 | }
548 | # If COM1 exists write msg to console.
549 | if ($global:write_to_serial) {
550 | Write-SerialPort -portname 'COM1' -data "$timestampped_msg" -ErrorAction SilentlyContinue
551 | }
552 | if ($is_error) {
553 | Write-Host "$timestampped_msg" -foregroundcolor red
554 | }
555 | elseif ($is_warning) {
556 | Write-Host "$timestampped_msg" -foregroundcolor yellow
557 | }
558 | else {
559 | Write-Host "$timestampped_msg"
560 | }
561 | }
562 | catch {
563 | _PrintError
564 | continue
565 | }
566 | }
567 |
568 |
569 | function Set-LogFile {
570 | param (
571 | [parameter(Position=0, Mandatory=$true)]
572 | [String]$filename
573 | )
574 | Write-Log "Initializing log file $filename."
575 | if (Test-Path $filename) {
576 | Write-Log 'Log file already exists.'
577 | $global:log_file = $filename
578 | }
579 | else {
580 | try {
581 | Write-Log 'Creating log file.'
582 | New-Item $filename -Type File -ErrorAction Stop
583 | $global:log_file = $filename
584 | }
585 | catch {
586 | _PrintError
587 | }
588 | }
589 | Write-Log "Log file set to $global:log_file"
590 | }
591 |
592 |
593 | # Export all modules.
594 | New-Alias -Name _WriteToSerialPort -Value Write-SerialPort
595 | New-Alias -Name _RunExternalCMD -Value Invoke-ExternalCommand
596 | New-Alias -Name _ClearEventLogs -Value Clear-EventLogs
597 | New-Alias -Name _ClearTempFolders -Value Clear-TempFolders
598 | New-Alias -Name _FetchFromMetadata -Value Get-Metadata
599 | Export-ModuleMember -Function * -Alias *
600 |
601 | if (_GetCOMPorts -portname 'COM1') {
602 | $global:write_to_serial = $true
603 | }
604 |
605 | # Clear out any existing errors.
606 | $error.Clear() | Out-Null
607 |
608 | # SIG # Begin signature block
609 | # MIIXsQYJKoZIhvcNAQcCoIIXojCCF54CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
610 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
611 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUFYCbOohI0jAoGr8qlHX8EbQb
612 | # OI6gghLXMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B
613 | # AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG
614 | # A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh
615 | # d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg
616 | # Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV
617 | # UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu
618 | # dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN
619 | # AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q
620 | # WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC
621 | # i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4
622 | # ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3
623 | # +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI
624 | # fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd
625 | # BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG
626 | # CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB
627 | # Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro
628 | # YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV
629 | # HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y
630 | # MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf
631 | # plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y
632 | # 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq
633 | # IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3
634 | # DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh
635 | # dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD
636 | # QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE
637 | # BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT
638 | # eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN
639 | # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow
640 | # mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0
641 | # jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu
642 | # ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh
643 | # d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz
644 | # C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB
645 | # o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO
646 | # BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw
647 | # Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90
648 | # cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx
649 | # oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy
650 | # bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV
651 | # HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa
652 | # 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH
653 | # bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73
654 | # BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR
655 | # EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW
656 | # yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu
657 | # e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw
658 | # ggTdMIIDxaADAgECAhAqnCGsqqY6PFinuTIr7pSNMA0GCSqGSIb3DQEBCwUAMH8x
659 | # CzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0G
660 | # A1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEwMC4GA1UEAxMnU3ltYW50ZWMg
661 | # Q2xhc3MgMyBTSEEyNTYgQ29kZSBTaWduaW5nIENBMB4XDTE1MTIxNjAwMDAwMFoX
662 | # DTE4MTIxNjIzNTk1OVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju
663 | # aWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAoMCkdvb2dsZSBJbmMx
664 | # EzARBgNVBAMMCkdvb2dsZSBJbmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
665 | # AoIBAQDEDYLEQSko5f0MP6XHDma9pcSLs4qshAOfhC443waxTv0zYFg4Nt0iz9/x
666 | # UB9H8VUFwYEB5yg+/1+JEgnq36oXSSxxq0jRnS70UeAD4PcWbHsMInVtfh9JxEMo
667 | # iEHcbO0TKgOZ62IU+TUmbhIsA+L3gbkaBWcGfKYaW+0gFeUtg96ONvoeCEEcGkif
668 | # tvHDLwITS6fKuu8cWG+O0w8UpAsrXbr0WqMNZDSlitePTSJmTaSu4fnNxljmxhF3
669 | # Mt+63zlIitEn1zN3qMnkXu36Es/z/fruq4CGEzTrWn5vbBvu2EuyzHeYh6zK9btk
670 | # b0keW5FjUB9jLYMncwefKxb0e3EpAgMBAAGjggFuMIIBajAJBgNVHRMEAjAAMA4G
671 | # A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzBmBgNVHSAEXzBdMFsG
672 | # C2CGSAGG+EUBBxcDMEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20v
673 | # Y3BzMCUGCCsGAQUFBwICMBkaF2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMB8GA1Ud
674 | # IwQYMBaAFJY7U/B5M5evfYPvLivMyreGHnJmMCsGA1UdHwQkMCIwIKAeoByGGmh0
675 | # dHA6Ly9zdi5zeW1jYi5jb20vc3YuY3JsMFcGCCsGAQUFBwEBBEswSTAfBggrBgEF
676 | # BQcwAYYTaHR0cDovL3N2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL3N2
677 | # LnN5bWNiLmNvbS9zdi5jcnQwEQYJYIZIAYb4QgEBBAQDAgQQMBYGCisGAQQBgjcC
678 | # ARsECDAGAQEAAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAj55OTr9uoTa+vVOjYJpWA
679 | # zSORcO0LW7Hp2N0eQDd4lxjtn+WEZ4UGULXxq+aDWhd7Ub5/GMZHXiuq9KAfNT4F
680 | # n0NA95/R9OGnAvOOyXH+GDdIQtfkNnMQktTY2RzEJlgYZ7YkImljAvdJUWt19rR9
681 | # Vv8s9Ij3Z28IhvOLCzACf22S2U69mfd7dIYMy7mtLL9EeagAgpxi9KoR39K/8OGS
682 | # KBGQu14ziIaWTd0Lr8NnoZUtRDLG+ve4gMFOOL4ftoT38SExZ0mon4p1B987OsPq
683 | # cs1Af6fafMkufKkM8V1cgkJiuUmUj3DmpcBfF/tANsE6iWMDHD9moD2PoUxOXKy/
684 | # MIIFWTCCBEGgAwIBAgIQPXjX+XZJYLJhffTwHsqGKjANBgkqhkiG9w0BAQsFADCB
685 | # yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
686 | # ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
687 | # U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
688 | # ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
689 | # aG9yaXR5IC0gRzUwHhcNMTMxMjEwMDAwMDAwWhcNMjMxMjA5MjM1OTU5WjB/MQsw
690 | # CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
691 | # BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVjIENs
692 | # YXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
693 | # ggEPADCCAQoCggEBAJeDHgAWryyx0gjE12iTUWAecfbiR7TbWE0jYmq0v1obUfej
694 | # DRh3aLvYNqsvIVDanvPnXydOC8KXyAlwk6naXA1OpA2RoLTsFM6RclQuzqPbROlS
695 | # Gz9BPMpK5KrA6DmrU8wh0MzPf5vmwsxYaoIV7j02zxzFlwckjvF7vjEtPW7ctZlC
696 | # n0thlV8ccO4XfduL5WGJeMdoG68ReBqYrsRVR1PZszLWoQ5GQMWXkorRU6eZW4U1
697 | # V9Pqk2JhIArHMHckEU1ig7a6e2iCMe5lyt/51Y2yNdyMK29qclxghJzyDJRewFZS
698 | # AEjM0/ilfd4v1xPkOKiE1Ua4E4bCG53qWjjdm9sCAwEAAaOCAYMwggF/MC8GCCsG
699 | # AQUFBwEBBCMwITAfBggrBgEFBQcwAYYTaHR0cDovL3MyLnN5bWNiLmNvbTASBgNV
700 | # HRMBAf8ECDAGAQH/AgEAMGwGA1UdIARlMGMwYQYLYIZIAYb4RQEHFwMwUjAmBggr
701 | # BgEFBQcCARYaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9jcHMwKAYIKwYBBQUHAgIw
702 | # HBoaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9ycGEwMAYDVR0fBCkwJzAloCOgIYYf
703 | # aHR0cDovL3MxLnN5bWNiLmNvbS9wY2EzLWc1LmNybDAdBgNVHSUEFjAUBggrBgEF
704 | # BQcDAgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRow
705 | # GAYDVQQDExFTeW1hbnRlY1BLSS0xLTU2NzAdBgNVHQ4EFgQUljtT8Hkzl699g+8u
706 | # K8zKt4YecmYwHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZI
707 | # hvcNAQELBQADggEBABOFGh5pqTf3oL2kr34dYVP+nYxeDKZ1HngXI9397BoDVTn7
708 | # cZXHZVqnjjDSRFph23Bv2iEFwi5zuknx0ZP+XcnNXgPgiZ4/dB7X9ziLqdbPuzUv
709 | # M1ioklbRyE07guZ5hBb8KLCxR/Mdoj7uh9mmf6RWpT+thC4p3ny8qKqjPQQB6rqT
710 | # og5QIikXTIfkOhFf1qQliZsFay+0yQFMJ3sLrBkFIqBgFT/ayftNTI/7cmd3/SeU
711 | # x7o1DohJ/o39KK9KEr0Ns5cF3kQMFfo2KwPcwVAB8aERXRTl4r0nS1S+K4ReD6bD
712 | # dAUK75fDiSKxH3fzvc1D1PFMqT+1i4SvZPLQFCExggREMIIEQAIBATCBkzB/MQsw
713 | # CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
714 | # BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVjIENs
715 | # YXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQQIQKpwhrKqmOjxYp7kyK+6UjTAJ
716 | # BgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0B
717 | # CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAj
718 | # BgkqhkiG9w0BCQQxFgQUEeKZi2AXA3etQbuOeFvREFxZ4nIwDQYJKoZIhvcNAQEB
719 | # BQAEggEAV4qbSXmlMNe8uP5tkPpfES6lxflrSHalr1+lEh9wfXrxR7LKvhdaOblM
720 | # rTxxQJBe6RGU3Ag86xWcByCQsUmHekCs2x1lTR/g3xFRWqpKvu4HIiB8iykZMgmu
721 | # aOvWBLBAxJ2gci1DELF+fTiBfmy8Jcz1UX0OOOwkgsvLbBVlU5SPZ150e0DdSfjh
722 | # nt3MVCBKDUNQ99hKDaWvqjdj/VX34/ExuEOBroxflTVWM21jMkRQ8aIEz940coGi
723 | # oB0l6GfGQy/VMsWgU7W4IEPpXH6gmI9EIwuzN+eim9N5xwGHqI/s/LzFpneX8pCX
724 | # 7jsAe9SYrWYU5LEoo0SmS/YcN4ymcqGCAgswggIHBgkqhkiG9w0BCQYxggH4MIIB
725 | # 9AIBATByMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh
726 | # dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD
727 | # QSAtIEcyAhAOz/Q4yP6/NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
728 | # CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODA3MTgxNjQ2MzRaMCMG
729 | # CSqGSIb3DQEJBDEWBBTDikzGBpDxGcjAeJBYtTj2DhEVbDANBgkqhkiG9w0BAQEF
730 | # AASCAQCLISKydxm1Pdur6eSZVdFTmG2+ift7J11+8fc10/TJr2VXv0tlrVgwadkw
731 | # hyp0ceXleVUXZ9sqw2D734jRPottGQKiQkQqBtTf8qF0NqpTXbhA7aBjFCC+wZXP
732 | # 0NkzGI9DLcLe6q+l2Mo7MY96jXID3bTjIBI52Mp1zEWDWhkeFjPCDdHWw03X4g0X
733 | # TXisAW/mHmMHYdXVXvDrY2ym9wyz4DIO4pWCSIdCA/4FT/G7yY0ba1H5N1hiLBd6
734 | # 9wl922GdZx00p7clwF2OZCH1jLTBfKaSd1NcUonG8oeoQiR2Lm3qoeMnc2uLdw0j
735 | # fwsDpip2ZPs2e7ws+jxs1UoNNc3g
736 | # SIG # End signature block
737 |
--------------------------------------------------------------------------------
/powershell/c2d/initial_win_startup_script.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Set-StrictMode -Version Latest
18 |
19 | $script:gce_install_dir = 'C:\Program Files\Google\Compute Engine\sysprep'
20 |
21 | # Import Modules
22 | try {
23 | Import-Module $script:gce_install_dir\gce_base.psm1 -ErrorAction Stop
24 | }
25 | catch [System.Management.Automation.ActionPreferenceStopException] {
26 | Write-Host $_.Exception.GetBaseException().Message
27 | Write-Host ("Unable to import GCE module from $script:gce_install_dir. " +
28 | 'Check error message, or ensure module is present.')
29 | exit 2
30 | }
31 |
32 | # Default Values
33 | $Script:c2d_scripts_bucket = 'c2d-windows/scripts'
34 | $Script:install_path="C:\C2D" # Folder for downloads
35 | $script:show_msgs = $false
36 | $script:write_to_serial = $false
37 |
38 |
39 | # Functions
40 | function DownloadScript {
41 | <#
42 | .SYNOPSIS
43 | Downloads a script to the localmachine from GCS.
44 | .DESCRIPTION
45 | Uses WebClient to download a script file.
46 | .EXAMPLE
47 | DownloadScript -path bucket/.. -filename
48 | #>
49 | param (
50 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
51 | $path,
52 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
53 | $filename,
54 | [Switch] $overwrite
55 | )
56 | $storage_url = 'http://storage.googleapis.com'
57 | $download_url = "$storage_url/$path"
58 |
59 |
60 | # Check if file already exists and act accordingly.
61 | if ((Test-path -path $filename)){
62 | if ($overwrite){
63 | Write-Log "$filename already exists. Overwrite flag set."
64 | _DeleteFiles -files $filename
65 | }
66 | else {
67 | Write-Log "$filename already exists. Overwrite flag notset."
68 | return $true
69 | }
70 | }
71 | # Download the file
72 | Write-Log "Original download url: $download_url"
73 | # To avoid cache issues
74 | $url = $download_url + "?random=" + (Get-Random).ToString()
75 |
76 | Write-Log "Downloading $url to $filename"
77 | try {
78 | Invoke-WebRequest -Uri $url -OutFile $filename -Headers @{"Cache-Control"="private"}
79 | }
80 | catch [System.Net.WebException] {
81 | $response = $_.Exception.Response
82 | if ($response) {
83 | _PrintError
84 | Write-Log $response.StatusCode -error # This is a System.Net.HttpStatusCode enum value
85 | Write-Log $response.StatusCode.value__ -error # This is the numeric version.
86 | }
87 | else {
88 | $type = $_.Exception.GetType().FullName
89 | $message = $_.Exception.Message
90 | Write-Log "$type $message"
91 | }
92 | return $false
93 | }
94 |
95 | # Check if download successfull
96 | if ((Test-path -path $filename)){
97 | return $true
98 | }
99 | else {
100 | Write-Log "File not found."
101 | return $false
102 | }
103 | }
104 |
105 |
106 | ## Main
107 | # Instance specific variables
108 | $script_name = 'sql_install.ps1'
109 | $script_subpath = 'sqlserver'
110 | $task_name = "SQLInstall"
111 |
112 | # Create the C:\C2D folder
113 | if (!(Test-path -path $Script:install_path )) {
114 | try {
115 | New-Item -ItemType directory -Path $Script:install_path
116 | }
117 | catch {
118 | _PrintError
119 | exit 1
120 | }
121 | }
122 |
123 | # Download the scripts
124 | # Base Script
125 | $base_script_path = "$Script:c2d_scripts_bucket/c2d_base.psm1"
126 | $base_script = "$Script:install_path\c2d_base.psm1"
127 | if (DownloadScript -path $base_script_path -filename $base_script) {
128 | Write-Log "File downloaded successfully."
129 | }
130 | else {
131 | Write-Log "File not found."
132 | exit 2
133 | }
134 | # Run Script
135 | $run_script = "$Script:install_path\$script_name"
136 | $run_script_path = "$Script:c2d_scripts_bucket/$script_subpath/$script_name"
137 | if (DownloadScript -path $run_script_path -filename $run_script) {
138 | Write-Log "File downloaded successfully."
139 | }
140 | else {
141 | Write-Log "File not found."
142 | exit 2
143 | }
144 |
145 |
146 | # Execute the script
147 | Write-Log "Checking if $task_name sctask exists?"
148 | $sc_task = Get-ScheduledTask -TaskName $task_name -ErrorAction SilentlyContinue
149 | if ($sc_task) {
150 | Write-Log "$task_name schtask exists."
151 | try {
152 | Write-Log "-- Executing sctask $task_name. --"
153 | $response = Start-ScheduledTask -TaskName $task_name
154 | Write-Log $response
155 |
156 | }
157 | catch {
158 | $type = $_.Exception.GetType().FullName
159 | $message = $_.Exception.Message
160 | Write-Log "$type $message"
161 | exit 1
162 | }
163 | }
164 | else {
165 | Write-Log "schtask $task_name does not exists."
166 | Write-Log "Executing: $run_script"
167 | try {
168 | & $run_script -task_name $task_name
169 | }
170 | catch {
171 | $type = $_.Exception.GetType().FullName
172 | $message = $_.Exception.Message
173 | Write-Log "$type $message"
174 | exit 1
175 | }
176 | }
--------------------------------------------------------------------------------
/powershell/c2d/sql_install.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | <#
18 | .SYNOPSIS
19 | SQL Server Configuration
20 |
21 | .DESCRIPTION
22 | This Script bootstrap SQL Server Configuration
23 | .EXAMPLE
24 | sql_bootstrap.ps1
25 | .EXAMPLE
26 | sql_bootstrap.ps1 -name
27 |
28 | #requires -version 3.0
29 | #>
30 | [CmdletBinding()]
31 | param (
32 | [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
33 | $task_name=$false,
34 | [Alias('AsJob')]
35 | [Switch] $as_job
36 | )
37 |
38 | Set-StrictMode -Version Latest
39 |
40 | $script:c2d_dir = 'C:\C2D'
41 |
42 | # Import Modules
43 | try {
44 | Import-Module $script:c2d_dir\c2d_base.psm1 -ErrorAction Stop
45 | }
46 | catch [System.Management.Automation.ActionPreferenceStopException] {
47 | Write-Host $_.Exception.GetBaseException().Message
48 | Write-Host ("Unable to import GCE module from $script:c2d_dir. " +
49 | 'Check error message, or ensure module is present.')
50 | exit 2
51 | }
52 |
53 | # Default Values
54 | $Script:all_nodes = @()
55 | $Script:all_nodes_csv = $null
56 | $Script:all_nodes_fqdn = @()
57 | $Script:backup_key_path = 'backup'
58 | $Script:cluster_name = 'cluster-dbclus'
59 | $Script:cluster_key_path = 'cluster'
60 | $Script:cred_obj = $null
61 | $Script:db_name = 'TestDB'
62 | $Script:db_folder_data = 'SQLData'
63 | $Script:db_folder_log = 'SQLLog'
64 | $Script:db_folder_backup = 'SQLBackup'
65 | $Script:initdb_key_path = 'initdb'
66 | $Script:domain_bios_name = $null
67 | $Script:domain_name = $null
68 | $script:domain_service_account = $null
69 | $script:name_ag = 'cluster-ag' # Name of the SQLServer Availability Group
70 | $script:name_ag_listener= 'cluster-listener'
71 | $script:node2 = $null
72 | $Script:replica_key_path = 'replica'
73 | $script:remote_nodes = @()
74 | $script:sa_password = $null
75 | $Script:static_ip = @()
76 | $Script:static_listner_ip = @()
77 | $Script:service_account = $null
78 | $script:show_msgs = $false
79 | $script:write_to_serial = $false
80 |
81 | # Functions
82 | function _AvailabilityReplica {
83 | Write-Logger "Creating SqlAvailabilityReplica on $Global:hostname"
84 | $initialized_nodes = @()
85 |
86 | # Find the version of SQL Server running
87 | $Srv = Get-Item SQLSERVER:\SQL\$($Global:hostname)\DEFAULT
88 | $Version = ($Srv.Version)
89 |
90 | try {
91 | ForEach($node in $Script:all_nodes) {
92 | # Create an in-memory representation of the primary replica
93 | $initialized_nodes += New-SqlAvailabilityReplica `
94 | -Name $node `
95 | -EndpointURL "TCP://$($node).$($Script:domain_name):5022" `
96 | -AvailabilityMode SynchronousCommit `
97 | -FailoverMode Automatic `
98 | -Version $Version `
99 | -AsTemplate
100 | }
101 | return $initialized_nodes
102 | }
103 | catch{
104 | Write-Logger $_.Exception.GetType().FullName
105 | Write-Logger "$_.Exception.Message"
106 | return $false
107 | }
108 | }
109 |
110 | function _BackUpDataBase {
111 | # Backup my database and its log on the primary
112 | Write-Logger "Creating backups of database $Script:db_name on $script:node2"
113 | try {
114 | # backup DB
115 | $backupDB = "\\$script:node2\$Script:db_folder_backup\$($Script:db_name)_db.bak"
116 | Backup-SqlDatabase `
117 | -Database $Script:db_name `
118 | -BackupFile $backupDB `
119 | -ServerInstance $Global:hostname `
120 | -Initialize
121 | # Backup log
122 | $backupLog = "\\$script:node2\$Script:db_folder_backup\$($db_name)_log.bak"
123 | Backup-SqlDatabase `
124 | -Database $Script:db_name `
125 | -BackupFile $backupLog `
126 | -ServerInstance $Global:hostname `
127 | -BackupAction Log -Initialize
128 | return $true
129 | }
130 | catch {
131 | Write-Logger $_.Exception.GetType().FullName
132 | Write-Logger "$_.Exception.Message"
133 | return $false
134 | }
135 | }
136 |
137 | function _CreateEndPoint {
138 | Write-Logger "Creating endpoint on node $Global:hostname"
139 | # Creating endpoint
140 | try {
141 | $endpoint = New-SqlHadrEndpoint "Hadr_endpoint" `
142 | -Port 5022 `
143 | -Path "SQLSERVER:\SQL\$Global:hostname\Default"
144 | Set-SqlHadrEndpoint -InputObject $endpoint -State "Started"
145 | return $true
146 | }
147 | catch [System.Data.SqlClient.SqlException] {
148 | Write-Logger "'Hadr_endpoint' already exists."
149 | return $true
150 | }
151 | catch {
152 | Write-Logger $_.Exception.GetType().FullName
153 | Write-Logger "$_.Exception.Message"
154 | return $false
155 | }
156 | }
157 |
158 | function _DBPermission {
159 | # Grant connect permissions to the endpoints
160 | $query = " `
161 | IF SUSER_ID('$($script:remote_nodes)') IS NULL CREATE LOGIN [$($script:remote_nodes)] FROM WINDOWS `
162 | GO
163 | GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [$($script:remote_nodes)] `
164 | GO `
165 | IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='AlwaysOn_health') `
166 | BEGIN `
167 | ALTER EVENT SESSION [AlwaysOn_health] ON SERVER WITH (STARTUP_STATE=ON); `
168 | END `
169 | IF NOT EXISTS(SELECT * FROM sys.dm_xe_sessions WHERE name='AlwaysOn_health') `
170 | BEGIN `
171 | ALTER EVENT SESSION [AlwaysOn_health] ON SERVER STATE=START; `
172 | END `
173 | GO "
174 |
175 | try {
176 | Write-Logger "$Global:hostname - Granting permission to endpoint"
177 | Write-Logger $query
178 | Invoke-Sqlcmd -Query $query
179 | return $true
180 | }
181 | catch {
182 | Write-Logger $_.Exception.GetType().FullName
183 | Write-Logger "$_.Exception.Message"
184 | return $false
185 | }
186 | }
187 |
188 | function _EnableAlwaysOn {
189 | # Enable Always-On on all Server nodes
190 | ForEach($node in $Script:all_nodes){
191 | try {
192 | Write-Logger "Sleeping for 10s ...."
193 | Start-Sleep -s 10
194 | Write-Logger "Trying to enable AlwaysOn feature for $node"
195 | Enable-SqlAlwaysOn -ServerInstance $node -Force
196 | Write-Logger "-- AlwaysOn feature turned on for $node .. --"
197 | }
198 | catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] {
199 | Write-Logger "ChangeHADRService failed for Service 'MSSQLSERVER' on node: $node"
200 | Write-logger "$Script:cluster_name is not setup correctly."
201 | return $false
202 | }
203 | catch {
204 | Write-Logger $_.Exception.GetType().FullName
205 | Write-Logger "$_.Exception.Message"
206 | return $false
207 | }
208 | }
209 | return $true
210 | }
211 |
212 | function _NewCluster {
213 |
214 | Write-Logger "Setting up cluster $Script:cluster_name for nodes $Script:all_nodes_fqdn and ips $Script:static_ip"
215 | # Create the cluster
216 | try {
217 | $result = New-Cluster -Name $Script:cluster_name -Node $Script:all_nodes_fqdn `
218 | -NoStorage -StaticAddress $Script:static_ip
219 | Write-Logger "Result for setup cluster: $result"
220 | return $true
221 | }
222 | catch {
223 | Write-Logger "** Failed to setup cluster: $Script:cluster_name ** "
224 | Write-Logger $_.Exception.GetType().FullName
225 | Write-Logger "$_.Exception.Message"
226 | return $false
227 | }
228 | }
229 |
230 | function _RestoreDataBase {
231 | Write-Logger "Restoring backups of database $Script:db_name on $Global:hostname"
232 |
233 | try {
234 | $backupDB = "\\$script:node2\$Script:db_folder_backup\$($Script:db_name)_db.bak"
235 | Write-Logger "Restoring DB from $backupDB"
236 | Restore-SqlDatabase `
237 | -Database $Script:db_name `
238 | -BackupFile $backupDB `
239 | -ServerInstance $Global:hostname `
240 | -NoRecovery -ReplaceDatabase
241 | # Restore Backup log
242 | $backupLog = "\\$script:node2\$Script:db_folder_backup\$($db_name)_log.bak"
243 | Write-Logger "Restoring Logs from $backupLog"
244 | Restore-SqlDatabase `
245 | -Database $Script:db_name `
246 | -BackupFile $backupLog `
247 | -ServerInstance $node2 `
248 | -RestoreAction Log `
249 | -NoRecovery
250 | return $true
251 | }
252 | catch {
253 | Write-Logger $_.Exception.GetType().FullName
254 | Write-Logger "$_.Exception.Message"
255 | return $false
256 | }
257 | }
258 |
259 | function CheckIfNode1 {
260 | <#
261 | .SYNOPSIS
262 | Checks if the current host is Node1
263 | .DESCRIPTION
264 | If the current host is node1 we do treat it as primary
265 | .EXAMPLE
266 | CheckIfNode1
267 | #>
268 |
269 | if ($Global:hostname.EndsWith(1)){
270 | return $true
271 | }
272 | }
273 |
274 | function ConfigureAvailabiltyGroup {
275 | <#
276 | .SYNOPSIS
277 | ConfigureAvailabiltyGroup
278 | .DESCRIPTION
279 | Configures the newly created availability group
280 | .EXAMPLE
281 | ConfigureAvailabiltyGroup
282 | #>
283 | $initialized_nodes = @()
284 | if ((_CreateEndPoint) -and (_DBPermission)) {
285 | Write-Logger "-- Availability endpoints are configured for all nodes. --"
286 |
287 | if (CheckIfNode1) { # Backup Primary database
288 | if (_BackUpDataBase) {
289 | UpdateSubWaiter -key "$Script:backup_key_path/success/done"
290 | }
291 | else {
292 | UpdateSubWaiter -key "$Script:backup_key_path/failure/failed"
293 | return $false
294 | }
295 | }
296 | else {
297 | # Restore primary db on other nodes
298 | if ((WaitForRuntime -path "$Script:backup_key_path/success/done" -timeout 12)) {
299 | if (_RestoreDataBase) {
300 | Write-Logger "$Script:db_name database restored successfully on $Global:hostname"
301 | UpdateSubWaiter -key "$Script:initdb_key_path/success/done"
302 | }
303 | else {
304 | Write-Logger "Failed to restore $Script:db_name on $Global:hostname"
305 | UpdateSubWaiter -key "$Script:initdb_key_path/failure/failed"
306 | return $false
307 | }
308 | }
309 | else {
310 | Write-Logger "TimeOut exceeded while waiting on node(s) to finish backup/restore operation."
311 | return $false
312 | }
313 | }
314 |
315 | # Create the New-SqlAvailabilityReplica
316 | if ((WaitForRuntime -path "$Script:initdb_key_path/success/done" -timeout 12)) {
317 | if (CheckIfNode1) {
318 | $initialized_nodes = _AvailabilityReplica
319 | if ($initialized_nodes) {
320 | Write-Logger ("Availability replica has been set.")
321 | UpdateSubWaiter -key "$Script:replica_key_path/success/done"
322 |
323 | # Create the availability group
324 | Write-Logger "-- Create Availability Group: $Script:name_ag --"
325 | try {
326 | New-SqlAvailabilityGroup `
327 | -Name $Script:name_ag `
328 | -Path "SQLSERVER:\SQL\$($Global:hostname)\DEFAULT" `
329 | -AvailabilityReplica $initialized_nodes `
330 | -Database $Script:db_name
331 | }
332 | catch{
333 | Write-Logger "** Failed to create SqlAvailabilityGroup. **"
334 | Write-Logger $_.Exception.GetType().FullName
335 | Write-Logger "$_.Exception.Message"
336 | return $false
337 | }
338 |
339 | # Join other nodes to availability group.
340 | Write-Logger "-- Joining nodes to: $Script:name_ag --"
341 | ForEach($node in $Script:all_nodes) {
342 | Write-Logger " adding $node to the $Script:name_ag"
343 | if ($node.EndsWith(1)) {
344 | Write-Logger "Primary $node does not needed to be added to $Script:name_ag."
345 | }
346 | else {
347 | try {
348 | Join-SqlAvailabilityGroup `
349 | -Path "SQLSERVER:\SQL\$($node)\DEFAULT" `
350 | -Name $Script:name_ag
351 | }
352 | catch {
353 | Write-Logger "** Failed to join $node in AvailabilityGroup. **"
354 | Write-Logger $_.Exception.GetType().FullName
355 | Write-Logger "$_.Exception.Message"
356 | }
357 |
358 | # Join the secondary database to the availability group.
359 | Write-Logger "-- Join DB in $node to Availability Group. --"
360 | try {
361 | Add-SqlAvailabilityDatabase `
362 | -Path "SQLSERVER:\SQL\$($node)\DEFAULT\AvailabilityGroups\$($Script:name_ag)" `
363 | -Database $Script:db_name
364 | }
365 | catch {
366 | Write-Logger "** Failed to join $Script:db_name on $node to $Script:name_ag. **"
367 | Write-Logger $_.Exception.GetType().FullName
368 | Write-Logger "$_.Exception.Message"
369 | }
370 | }
371 | }
372 |
373 | # Create the listener
374 | Write-Logger "-- Create Listener with IPs: $Script:static_listner_ip. --"
375 | try {
376 | New-SqlAvailabilityGroupListener `
377 | -Name $name_ag_listener `
378 | -StaticIp $Script:static_listner_ip `
379 | -Path SQLSERVER:\SQL\$($Global:hostname)\DEFAULT\AvailabilityGroups\$($Script:name_ag)
380 | }
381 | catch{
382 | Write-Logger "** Failed to add listeners to $Script:name_ag. **"
383 | Write-Logger $_.Exception.GetType().FullName
384 | Write-Logger "$_.Exception.Message"
385 | return $false
386 | }
387 | }
388 | else {
389 | Write-Logger "** Failed to initialize all db in sqlcluster **"
390 | UpdateSubWaiter -key "$Script:replica_key_path/failure/failed"
391 | return $false
392 | }
393 | }
394 | }
395 | else {
396 | Write-Logger "TimeOut exceeded while waiting on nodes to initialize db."
397 | return $false
398 | }
399 |
400 | # Check availability group
401 | if ((WaitForRuntime -path "$Script:replica_key_path/success/done" -timeout 12)) {
402 | Write-Logger "Waiting.."
403 | }
404 | else {
405 | return $true
406 | }
407 | }
408 | else {
409 | Write-Logger "Failed to create endpoints"
410 | return $false
411 | }
412 | }
413 |
414 | function CreateShares {
415 | <#
416 | .SYNOPSIS
417 | Creates Folders and shares on local machine
418 | .DESCRIPTION
419 | Creates folder and shares for SQL server HA needs
420 | .EXAMPLE
421 | CreateShares
422 | #>
423 |
424 | if (Test-Path -path $shares_already_created_reg) {
425 | Write-Log "Shares are already created. Nothing to do here..."
426 | }
427 | else {
428 | # Configure SQL Folders
429 | Write-Log "Create SQL Share Folders $Script:db_folder_data & $Script:db_folder_log"
430 | New-Item -ItemType directory -Path "C:\$Script:db_folder_data"
431 | New-Item -ItemType directory -Path "C:\$Script:db_folder_log"
432 |
433 | if ($Global:hostname.EndsWith(2)){ # Create backup share on node2 only
434 | Write-Log "Creating backup share $Script:db_folder_backup as $global:hostname is not the primary node."
435 | New-Item -ItemType directory -Path "C:\$Script:db_folder_backup"
436 | try {
437 | New-SMBShare -Name "$Script:db_folder_backup" -Path "C:\$Script:db_folder_backup" `
438 | -FullAccess 'Everyone'
439 | }
440 | catch {
441 | _PrintError
442 | }
443 |
444 | # Enable CredSSP on localhost and disable name checking
445 | if (!(CheckIfNode1)) {
446 | Write-Log "Enable CredSSP Client on $Global:hostname"
447 | try {
448 | Enable-WSManCredSSP Client -DelegateComputer * -Force
449 | # Wait 15 secs before enabling CredSSP in both servers
450 | # On occasions got errors when running the command that follows without waiting
451 | Start-Sleep -s 15
452 | }
453 | catch {
454 | _PrintError
455 | }
456 | }
457 | }
458 | # Enable CredSSP Server in remote nodes
459 | Write-Log "Enable CredSSP Server nodes on: $Global:Hostname"
460 | Enable-WSManCredSSP Server -Force
461 | # On all Nodes
462 | Write-ToReg $shares_already_created_reg
463 | }
464 | }
465 |
466 | function CreateTestDB {
467 | <#
468 | .SYNOPSIS
469 | Create a testDB
470 | .DESCRIPTION
471 | Creates a TestDB on the machines. Script based on:
472 | https://github.com/sqlthinker/dotnet-docs-samples/blob/master/compute/sqlserver/powershell/create-availability-group.ps1
473 | .EXAMPLE
474 | CreateTestDB
475 | #>
476 |
477 | $sql_data = "C:\$Script:db_folder_data" # Directory to store the database data files
478 | $sql_log = "C:\$Script:db_folder_log" # Directory to store the database transaction log files
479 | $data_size = 1024 # Initial size of the database in MB
480 | $data_growth = 256 # Auto growth size of the database in MB
481 | $log_size = 1024 # Initial size of the transaction log in MB
482 | $log_growth = 256
483 |
484 | try {
485 | Write-Log "Disable Name Checking on $Global:hostname"
486 | Import-Module SQLPS -DisableNameChecking
487 | $objServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server `
488 | -ArgumentList 'localhost'
489 | }
490 | catch {
491 | _PrintError
492 | return $false
493 | }
494 |
495 | # Only continue if the database does not exist
496 | $objDB = $objServer.Databases[$Script:db_name]
497 | if (!($objDB)) {
498 | Write-Log "$Global:hostname - Creating the database $db_name"
499 | $objDB = New-Object `
500 | -TypeName Microsoft.SqlServer.Management.Smo.Database($objServer, $db_name)
501 |
502 | # Create the primary file group and add it to the database
503 | $objPrimaryFG = New-Object `
504 | -TypeName Microsoft.SqlServer.Management.Smo.Filegroup($objDB, 'PRIMARY')
505 | $objDB.Filegroups.Add($objPrimaryFG)
506 |
507 | # Create a single data file and add it to the Primary file group
508 | $dataFileName = $Script:db_name + '_Data'
509 | $objData = New-Object `
510 | -TypeName Microsoft.SqlServer.Management.Smo.DataFile($objPrimaryFG, $dataFileName)
511 | $objData.FileName = $sql_data + '\' + $dataFileName + '.mdf'
512 | $objData.Size = ($data_size * 1024)
513 | $objData.GrowthType = 'KB'
514 | $objData.Growth = ($data_growth * 1024)
515 | $objData.IsPrimaryFile = 'true'
516 | $objPrimaryFG.Files.Add($objData)
517 |
518 | # Create the log file and add it to the database
519 | $logName = $Script:db_name + '_Log'
520 | $objLog = New-Object Microsoft.SqlServer.Management.Smo.LogFile($objDB, $logName)
521 | $objLog.FileName = $sql_log + '\' + $logName + '.ldf'
522 | $objLog.Size = ($log_size * 1024)
523 | $objLog.GrowthType = 'KB'
524 | $objLog.Growth = ($log_growth * 1024)
525 | $objDB.LogFiles.Add($objLog)
526 |
527 | # Create the database
528 | $objDB.Script() # Show a script with the command we are about to run
529 | $objDB.Create() # Create the database
530 | $objDB.SetOwner('sa') # Change the owner to sa
531 | }
532 | else {
533 | Write-Log "$Script:db_name DB already exists on $Global:hostname. Skipping ..."
534 | }
535 | }
536 |
537 | function InstallServerComponents {
538 | <#
539 | .SYNOPSIS
540 | Install all components needed for SQL Server Setup.
541 | .DESCRIPTION
542 | All install-windows feature and modules required on all nodes.
543 | .EXAMPLE
544 | InstallServerComponents
545 | #>
546 |
547 | Write-Log "Installing Server Components ..."
548 | try {
549 | # We may need to remove AD objects, so we will need the RSAT-AD-PowerShell
550 | Install-WindowsFeature RSAT-AD-PowerShell
551 | Install-WindowsFeature Failover-Clustering -IncludeManagementTools
552 | return $true
553 | }
554 | catch {
555 | Write-Log $_.Exception.GetType().FullName -error
556 | Write-Log "$_.Exception.Message"
557 | return $false
558 | }
559 | }
560 |
561 | function JoinDomain {
562 | <#
563 | .SYNOPSIS
564 | Join current machine to domain.
565 | .DESCRIPTION
566 | Attempts to join the current machine domain
567 | .EXAMPLE
568 | JoinDomain
569 | #>
570 |
571 | Write-Log "Fetching Domain join parameters."
572 | $SA_PASSWORD = (ConvertTo-SecureString (_FetchFromMetaData `
573 | -property 'attributes/c2d-property-sa-password') -AsPlainText -Force)
574 |
575 | $credential = New-Object System.Management.Automation.PSCredential($script:domain_service_account, $SA_PASSWORD)
576 | Write-Log "Attempting to join $global:hostname to $Script:domain_name."
577 | try {
578 | Add-Computer -DomainName $Script:domain_name -Credential $credential
579 | return $true
580 | }
581 | catch {
582 | _PrintError
583 | return $false
584 | }
585 | }
586 |
587 | function SetIP{
588 | <#
589 | .SYNOPSIS
590 | Set local machine IP, Gateway and Firewall
591 | .DESCRIPTION
592 | Set IP address, Gateway, and Firewall
593 | .EXAMPLE
594 | SetIP
595 | #>
596 |
597 | Write-Log "Getting Current IP settings on $global:hostname."
598 | try {
599 | $current_ip = (Get-NetIPConfiguration | `
600 | Where InterfaceAlias -eq 'Ethernet').IPv4Address.IPAddress
601 | Write-Log "Current IP Address: $current_ip"
602 |
603 | $current_gateway = (Get-NetIPConfiguration | `
604 | Where InterfaceAlias -eq 'Ethernet').Ipv4DefaultGateway.NextHop
605 | Write-Log "Current GateWay Address: $current_gateway"
606 | }
607 | catch {
608 | _PrintError
609 | return $false
610 | }
611 |
612 | try {
613 | Write-Log "Setting Static IP on $global:hostname."
614 | _RunExternalCMD netsh interface ip set address name=Ethernet static $current_ip 255.255.0.0 $current_gateway 1
615 |
616 | Start-Sleep -Seconds 10
617 |
618 | Write-Log "Setting DNS $global:hostname."
619 | _RunExternalCMD netsh interface ip set dns Ethernet static 10.0.0.100
620 |
621 | Write-Log "Opening up SQL-Server specific Firewall ports $global:hostname."
622 | _RunExternalCMD netsh advfirewall firewall add rule `
623 | name="Open Port 5022 for Availability Groups" dir=in action=allow protocol=TCP localport=5022
624 | _RunExternalCMD netsh advfirewall firewall add rule `
625 | name="Open Port 1433 for SQL Server" dir=in action=allow protocol=TCP localport=1433
626 | }
627 | catch {
628 | _PrintError
629 | return $false
630 | }
631 | return $true
632 | }
633 |
634 | function SetScriptVar {
635 | <#
636 | .SYNOPSIS
637 | Initialize all necessary script variables.
638 | .DESCRIPTION
639 | Called once at the beginning of the script to initialize $Script:x
640 | .EXAMPLE
641 | SetScriptVar
642 | #>
643 |
644 | # Set Service Account
645 | $Script:service_account = _FetchFromMetaData -property 'attributes/c2d-property-sa-account'
646 |
647 | # Set DomainName Properties
648 | $Script:domain_name = _FetchFromMetaData -property 'attributes/c2d-property-domain-dns-name'
649 | $Script:domain_bios_name = $Script:domain_name.split(".")[0]
650 | $script:domain_service_account = "$Script:domain_bios_name\$Script:service_account"
651 | $script:sa_password = _FetchFromMetaData -property 'attributes/c2d-property-sa-password'
652 |
653 | # Get all nodes
654 | $Script:all_nodes = ((_FetchFromMetaData -property 'attributes/sql-nodes').split("|")).Where({ $_ -ne "" })
655 |
656 | # Add FQDN and get static ip address
657 | $ip_count = 1
658 | ForEach ($host_node in $all_nodes) {
659 | $Script:all_nodes_fqdn += "$host_node.$Script:domain_name"
660 | $Script:static_ip += "10.$ip_count.1.4"
661 | $Script:static_listner_ip += "10.$ip_count.1.5/255.255.0.0"
662 | $ip_count++
663 | if (!($host_node -eq $Global:hostname)) {
664 | $script:remote_nodes += "$($Script:domain_bios_name)\$($host_node)`$"
665 | }
666 | }
667 |
668 | $script:node2 = $all_nodes[1]
669 |
670 | # Create PS CRED object
671 | $Pwd = ConvertTo-SecureString $script:sa_password -AsPlainText -Force
672 | $Script:cred_obj = New-Object System.Management.Automation.PSCredential $script:domain_service_account, $Pwd
673 | }
674 |
675 | function SetupCluster {
676 | <#
677 | .SYNOPSIS
678 | Setup New Cluster
679 | .DESCRIPTION
680 | Setups a new cluster and enables availability group
681 | .EXAMPLE
682 | SetupCluster
683 | #>
684 | $if_exists = $null
685 | $retry_attempt = $null
686 | $no_of_try =
687 |
688 | # This loop is to catch an edge case where _NewCluster setup exists
689 | # without any error message and does not setup cluster.
690 | for ($retry_attempt=0; $retry_attempt -le 1; $retry_attempt++) {
691 | if(_NewCluster) { # Run the setup command.
692 | try {
693 | $if_exists = (Get-Cluster -ErrorAction SilentlyContinue).Name
694 | if ($if_exists) {
695 | Write-Logger "-- $if_exists new cluster setup complete. --"
696 | break
697 | }
698 | }
699 | catch [System.Management.Automation.PropertyNotFoundException] {
700 | # This block will run if NewCluster ran without any errors.
701 | Write-Logger "## Cluster $Script:cluster_name is not configured. Will retry again, attempt: $retry_attempt .. ##"
702 | continue
703 | }
704 | catch {
705 | Write-Logger $_.Exception.GetType().FullName
706 | Write-Logger "$_.Exception.Message"
707 | return $false
708 | }
709 | }
710 | else { # if the NewCluster command fails exit
711 | Write-Logger "** NewCluster setup failed. **"
712 | return $false
713 | }
714 | }
715 |
716 | if($if_exists) {
717 | if(_EnableAlwaysOn){
718 | Write-Logger "-- Always on enabled for all nodes in cluster. --"
719 | return $true
720 | }
721 | else{
722 | Write-Logger "** Turning on AlwaysOn feature for nodes:$Script:all_nodes. **"
723 | return $false
724 | }
725 | }
726 | else{
727 | Write-Logger "** Cluster setup failed for unknown reason. **"
728 | return $false
729 | }
730 | }
731 |
732 | function UpdateSubWaiter {
733 | <#
734 | .SYNOPSIS
735 | UpdateSubWaiter
736 | .DESCRIPTION
737 | Updates sub runtime waiters.
738 | .EXAMPLE
739 | UpdateSubWaiter -key
740 | #>
741 | param (
742 | [Alias('key')]
743 | [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
744 | $key_path
745 | )
746 |
747 | CreateRunTimeVariable -config_path (_GetRuntimeConfig) -var_name "$key_path" /failure/failed
748 | #TODO remove the following 2 lines
749 | $path=_GetRuntimeConfig
750 | Write-Logger "RuntimeConfigPath is $path"
751 | }
752 |
753 |
754 | ## Mainregi
755 |
756 | # Initialize Script variables
757 | SetScriptVar
758 |
759 | # Set registry paths
760 | $sql_on_domain_reg = "HKLM:\SOFTWARE\Google\SQLOnDomain"
761 | $sql_configured_reg = "HKLM:\SOFTWARE\Google\SQLServerConfigured"
762 | $sql_server_task = "HKLM:\SOFTWARE\Google\SQLServerTask"
763 | $shares_already_created_reg = "HKLM:\SOFTWARE\Google\SharesCreated"
764 |
765 | if($as_job){ # Run as Scheduled Task.
766 | if (Test-Path -path $sql_configured_reg) {
767 | Write-Logger "$global:hostname sql node is already configured. Nothing to do here."
768 | exit 0
769 | }
770 |
771 | # Lets create the cluster as a Scheduled task in the service account context
772 | Write-Logger "Attempting to install cluster on $Global:hostname"
773 | Write-Logger "-- Running as $env:UserName --"
774 |
775 | if (CheckIfNode1){ # This command runs on node1
776 | if (SetupCluster) {
777 | Write-Logger "Cluster setup was successful on $Global:hostname"
778 | Write-Logger "----------------------------"
779 | Write-Logger "SQL Cluster install finished on $Global:hostname."
780 | Write-Logger "----------------------------"
781 | UpdateSubWaiter -key "$Script:cluster_key_path/success/done"
782 | }
783 | else {
784 | Write-Logger "** SetupCluster step failed ***"
785 | UpdateSubWaiter -key "$Script:cluster_key_path/failure/failed"
786 | UpdateRunTimeWaiter -path status -failure
787 | exit
788 | }
789 | }
790 | else { # All Secondary nodes
791 | Write-Logger "Waiting for cluster setup."
792 | if ((WaitForRuntime -path "$Script:cluster_key_path/success/done" -timeout 10)){
793 | Write-Logger "Cluster Setup finished on primary node"
794 | }
795 | else {
796 | Write-Logger "** Something went wrong during primary cluster setup. **"
797 | UpdateRunTimeWaiter -path status -failure
798 | exit
799 | }
800 | }
801 |
802 | # Run this for all nodes
803 | if (ConfigureAvailabiltyGroup){
804 | # All Done
805 | Write-Logger "----------------------------"
806 | Write-Logger " AG install finished on $Global:hostname."
807 | Write-Logger "----------------------------"
808 | Write-ToReg $sql_configured_reg
809 | UpdateRunTimeWaiter -path status
810 | exit
811 | }
812 | else {
813 | Write-Logger "*** Failed to create Availability Group. ***"
814 | UpdateRunTimeWaiter -failure
815 | UpdateRunTimeWaiter -path status -failure
816 | }
817 | }
818 |
819 |
820 | # Do SQL Server Installs
821 | if (Test-Path -path $sql_on_domain_reg) {
822 | # Check if first bootup. If yes, do following steps.
823 | Write-Log "$global:hostname sql node is joined to the $Script:domain_name domain." -important
824 | }
825 | else { # Join the machine to the domain
826 | Write-Log "We are live from SQL Server nodes."
827 | # Set Static IP Address
828 | if (SetIP) {
829 | UpdateRunTimeWaiter
830 | }
831 | else {
832 | UpdateRunTimeWaiter -failure
833 | }
834 | if (JoinDomain) {
835 | UpdateRunTimeWaiter
836 | # Create registry key so this block is not run again.
837 | Write-ToReg $sql_on_domain_reg
838 |
839 | Write-Log "Rebooting $global:hostname"
840 | Restart-Computer
841 | exit
842 | }
843 | else {
844 | UpdateRunTimeWaiter -failure
845 | }
846 | }
847 |
848 | # Configure SQL Server after domain join
849 | if (Test-Path -path $sql_configured_reg) {
850 | Write-Log "$global:hostname sql node is already configured. Nothing to do here." -important
851 | exit 0
852 | }
853 | elseif ($task_name -and (!(Test-Path $sql_server_task ))) {
854 | Write-Log "Need to configure node for fail-over clustering."
855 | CreateShares
856 | InstallServerComponents
857 | Write-Log "Installed all necessary components"
858 | if (CheckIfNode1){ # Create TestDB on Node1
859 | Write-Log "Creating Local Database" -important
860 | CreateTestDB
861 | }
862 |
863 | # First Check if the scheduled task already exists?
864 | $sc_task = Get-ScheduledTask -TaskName $task_name -ErrorAction SilentlyContinue
865 | if ($sc_task) {
866 | Write-Log "-- $task_name scheduled task already exists. --"
867 | }
868 | else { # Create the scheduled task
869 | Write-Log "Create schtask: $task_name with file $PSCommandPath"
870 | CreateSCTask -name $task_name -user $script:domain_service_account -password $script:sa_password -file $PSCommandPath
871 | Start-Sleep -Seconds 5
872 |
873 | # Create registry key so this block is not run again
874 | Write-ToReg $sql_server_task
875 |
876 | Start-ScheduledTask -TaskName $task_name
877 | Write-Log "Scheduled task $task_name finished running."
878 | }
879 | }
880 | else {
881 | Write-Log "All SQL steps are done. For cluster setup run $PSCommandPath -AsJob"
882 | }
883 |
--------------------------------------------------------------------------------
/powershell/comprehensive-runtime-config.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Function Set-RuntimeConfigVariable {
18 | Param(
19 | [Parameter(Mandatory=$True)][String] $ConfigPath,
20 | [Parameter(Mandatory=$True)][String] $Variable,
21 | [Parameter(Mandatory=$True)][String] $Text
22 | )
23 |
24 | $Auth = $(gcloud auth print-access-token)
25 |
26 | $Path = "$ConfigPath/variables"
27 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$Path"
28 |
29 | $Json = (@{
30 | name = "$Path/$Variable"
31 | text = $Text
32 | } | ConvertTo-Json)
33 |
34 | $Headers = @{
35 | Authorization = "Bearer " + $Auth
36 | }
37 |
38 | $Params = @{
39 | Method = "POST"
40 | Headers = $Headers
41 | ContentType = "application/json"
42 | Uri = $Url
43 | Body = $Json
44 | }
45 |
46 | Try {
47 | Return Invoke-RestMethod @Params
48 | }
49 | Catch {
50 | $Reader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
51 | $ErrResp = $Reader.ReadToEnd() | ConvertFrom-Json
52 | $Reader.Close()
53 | Return $ErrResp
54 | }
55 |
56 | }
57 |
58 | Function Get-RuntimeConfigWaiter {
59 | Param(
60 | [Parameter(Mandatory=$True)][String] $ConfigPath,
61 | [Parameter(Mandatory=$True)][String] $Waiter
62 | )
63 |
64 | $Auth = $(gcloud auth print-access-token)
65 |
66 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters/$Waiter"
67 | $Headers = @{
68 | Authorization = "Bearer " + $Auth
69 | }
70 | $Params = @{
71 | Method = "GET"
72 | Headers = $Headers
73 | Uri = $Url
74 | }
75 | Write-Host "$Url"
76 |
77 | Return Invoke-RestMethod @Params
78 | }
79 |
80 | Function Wait-RuntimeConfigWaiter {
81 | Param(
82 | [Parameter(Mandatory=$True)][String] $ConfigPath,
83 | [Parameter(Mandatory=$True)][String] $Waiter,
84 | [int] $Sleep = 60
85 | )
86 | $RuntimeWaiter = $Null
87 |
88 | Write-Host $ConfigPath/waiters/$Waiter
89 |
90 | While (($RuntimeWaiter -eq $Null) -Or (-Not $RuntimeWaiter.done)) {
91 | $RuntimeWaiter = Get-RuntimeConfigWaiter -ConfigPath $ConfigPath -Waiter $Waiter
92 | If (-Not $RuntimeWaiter.done) {
93 | Write-Host "Waiting for [$ConfigPath/waiters/$Waiter]..."
94 | Sleep $Sleep
95 | }
96 | }
97 | Return $RuntimeWaiter
98 | }
99 |
100 | Function Create-RuntimeConfigWaiter {
101 | Param(
102 | [Parameter(Mandatory=$True)][String] $ConfigPath,
103 | [Parameter(Mandatory=$True)][String] $Waiter,
104 | [Parameter(Mandatory=$True)][String] $Timeout,
105 | [Parameter(Mandatory=$True)][String] $SuccessPath,
106 | [Parameter(Mandatory=$True)][Int] $SuccessCardinality,
107 | [Parameter(Mandatory=$False)][String] $FailurePath = "",
108 | [Parameter(Mandatory=$False)][Int] $FailureCardinality=0
109 | )
110 |
111 | $RuntimeWaiter = $Null
112 |
113 | Write-Host $ConfigPath/waiters/$Waiter
114 |
115 | $Auth = $(gcloud auth print-access-token)
116 |
117 |
118 | if($FailurePath.Length -eq 0){
119 | $Body = "{timeout: '" + $Timeout + "s', name: '$ConfigPath/waiters/$Waiter', success: { cardinality: { number: $SuccessCardinality, path: '$SuccessPath' }}}"
120 | }else{
121 | $Body = "{timeout: '" + $Timeout + "s', name: '$ConfigPath/waiters/$Waiter', `
122 | success: { cardinality: { number: $SuccessCardinality, path: '$SuccessPath' }}, `
123 | failure: { cardinality: { number: $FailureCardinality, path: '$FailurePath' }}}"
124 | }
125 |
126 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters"
127 |
128 | $Headers = @{
129 | Authorization="Bearer " + $Auth
130 | }
131 | $Params = @{
132 | Method = "POST"
133 | Headers = $Headers
134 | Uri = $Url
135 | Body=$Body
136 | }
137 | Write-Host "$Url"
138 | # Write-Host "$Params"
139 |
140 | #Return Invoke-RestMethod $Params
141 | Return Invoke-RestMethod -Uri $Url -Headers $Headers -Method 'Post' -Body $Body -ContentType "application/json"
142 | #Return $RuntimeWaiter
143 | }
144 |
145 | Function Delete-RuntimeConfigWaiter {
146 | Param(
147 | [Parameter(Mandatory=$True)][String] $ConfigPath,
148 | [Parameter(Mandatory=$True)][String] $Waiter
149 | )
150 |
151 | $RuntimeWaiter = $Null
152 |
153 | Write-Host $ConfigPath/waiters/$Waiter
154 |
155 | $Auth = $(gcloud auth print-access-token)
156 |
157 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters/$Waiter"
158 |
159 | $Headers = @{
160 | Authorization="Bearer " + $Auth
161 | }
162 |
163 | Write-Host "$Url"
164 |
165 | Return Invoke-RestMethod -Uri $Url -Headers $Headers -Method 'Delete'
166 | }
167 |
168 |
169 | Function List-RuntimeConfigWaiter {
170 | Param(
171 | [Parameter(Mandatory=$True)][String] $ConfigPath
172 | )
173 |
174 | $RuntimeWaiter = $Null
175 |
176 | Write-Host $ConfigPath/waiters
177 |
178 | $Auth = $(gcloud auth print-access-token)
179 |
180 | $Url = "https://runtimeconfig.googleapis.com/v1beta1/$ConfigPath/waiters"
181 |
182 | $Headers = @{
183 | Authorization="Bearer " + $Auth
184 | }
185 |
186 | Write-Host "$Url"
187 |
188 | Return Invoke-RestMethod -Uri $Url -Headers $Headers -Method 'Get'
189 | }
190 |
191 | Function DeleteAllWaiters{
192 | Param(
193 | [Parameter(Mandatory=$True)][String] $ConfigPath
194 | )
195 |
196 | $List = List-RuntimeConfigWaiter -ConfigPath $RuntimeConfig
197 |
198 | foreach($waiter in $List.waiters){
199 | $waiterName=$waiter.name.Substring($waiter.name.LastIndexOf("/")+1, $waiter.name.Length - $waiter.name.LastIndexOf("/")-1)
200 | Write-Host $waiterName
201 | Delete-RuntimeConfigWaiter -ConfigPath $ConfigPath -Waiter $waiterName
202 | }
203 | }
204 |
205 | $RuntimeConfig = "projects/{project-name}/configs/acme-config"
206 | $Waiter = "waiter41"
207 |
208 | $Result = List-RuntimeConfigWaiter -ConfigPath $RuntimeConfig
209 |
210 | foreach($waiter in $Result.waiters){
211 | Write-Host $waiter
212 | }
213 |
214 | $DeleteResult=DeleteAllWaiters -ConfigPath $RuntimeConfig
215 |
216 | try{
217 | Delete-RuntimeConfigWaiter -ConfigPath $RuntimeConfig `
218 | -Waiter $Waiter
219 | }Catch{
220 | Write-Host "Error"
221 | Write-Host $_.Exception.Message
222 | }
223 |
224 |
225 | try{
226 | Create-RuntimeConfigWaiter -ConfigPath $RuntimeConfig `
227 | -Waiter $Waiter `
228 | -Timeout 100 `
229 | -SuccessPath 'bootstrap/acme-sandbox-win-p-01/success' `
230 | -SuccessCardinality 1
231 | #-FailurePath 'bootstrap/acme-sandbox-win-p-01/failure' `
232 | #-FailureCardinality 1
233 | }Catch{
234 | Write-Host "Error"
235 | Write-Host $_.Exception.Message
236 | }
237 |
238 |
239 | try{
240 | Wait-RuntimeConfigWaiter -ConfigPath $RuntimeConfig -Waiter $Waiter
241 | #$thewaiter = Get-RuntimeConfigWaiter -ConfigPath $RuntimeConfig -Waiter $Waiter
242 | Write-Host $thewaiter
243 | } Catch{
244 | Write-Host "Error"
245 | Write-Host $_.Exception.Message
246 | }
247 |
248 | Write-Host "Bootstrap script ended..."
249 |
--------------------------------------------------------------------------------
/powershell/templates/windows-stackdriver-setup.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 |
18 |
19 | $tempdir = "c:\temp\"
20 | $tempdir = $tempdir.tostring()
21 | $appToMatch = 'Stackdriver*'
22 | $msiFile = "C:\Windows\system32\msiexec.exe"
23 |
24 | $LOG='c:\temp\install.log'
25 |
26 | #function to write debugging info to the console
27 | Function Write-SerialPort ([string] $message) {
28 | $port = new-Object System.IO.Ports.SerialPort COM1,9600,None,8,one
29 | $port.open()
30 | $port.WriteLine($message)
31 | $port.Close()
32 | }
33 |
34 | function Get-InstalledApps
35 | {
36 | if ([IntPtr]::Size -eq 4) {
37 | $regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
38 | }
39 | else {
40 | $regpath = @(
41 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
42 | 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
43 | )
44 | }
45 | Get-ItemProperty $regpath | .{process{if($_.DisplayName -and $_.UninstallString) { $_ } }} | Select DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString |Sort DisplayName
46 | }
47 |
48 | Write-SerialPort "Environment passed in was: ${environment}"
49 |
50 | #is stackdriver installed
51 | $result = Get-InstalledApps | where {$_.DisplayName -like $appToMatch}
52 |
53 | #if we are not admin
54 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
55 | {
56 | # Relaunch as an elevated process:
57 | Write-SerialPort "Elevating"
58 | Start-Process powershell.exe "-File",('"{0}"' -f $MyInvocation.MyCommand.Path) -Verb RunAs
59 | exit
60 | }
61 |
62 | Write-SerialPort "Prefix: ${environment} Name: ${projectname} comes from the project itself"
63 | Write-SerialPort "Elevated"
64 |
65 | Write-Host "Bootstrap script started..."
66 |
67 |
68 | Write-Host "Getting network config..."
69 | # reconfigure dhcp address as static to avoid warnings during dcpromo
70 | $IpAddr = Get-NetIPAddress -InterfaceAlias Ethernet
71 | $IpConf = Get-NetIPConfiguration -InterfaceAlias Ethernet
72 |
73 | Write-SerialPort "Fetching metadata parameters..."
74 | $Domain = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/domain-name
75 | #$NetBiosName = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/netbios-name
76 | $KmsKey = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/kms-key
77 | $KmsRegion = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/keyring-region
78 | $GcsPrefix = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/gcs-prefix
79 |
80 | Write-SerialPort $Domain, $IpAddr, $GcsPrefix
81 |
82 | $tempPath = "c:\temp\"
83 |
84 | Write-Host "temp dir: $tempPath"
85 |
86 | if((Test-Path $tempPath) -eq 0){
87 | New-Item -ItemType directory -Path c:\temp\
88 | Write-Host "created c:\temp"
89 | }
90 |
91 | cd C:\temp\
92 |
93 | $tempSDPath = "c:\temp\StackdriverMonitoring-GCM-46.exe"
94 |
95 | #get stackdriver
96 | if((Test-Path $tempSDPath) -eq 0){
97 | Write-Host("Downloading stackdriver agent")
98 | invoke-webrequest https://repo.stackdriver.com/windows/StackdriverMonitoring-GCM-46.exe -OutFile $tempSDPath;
99 | }else{
100 | Write-Host("Stackdriver Agent already downloaded")
101 | }
102 |
103 | Write-SerialPort("Get installed apps")
104 |
105 | $appToMatch = "StackdriverAgent"
106 |
107 | $result = Get-Process | where {$_.ProcessName -like $appToMatch}
108 |
109 | # Now running elevated so launch the script:
110 | If ($result -eq $null) {
111 | Write-Host "Running the Stackdriver install"
112 | .\StackdriverMonitoring-GCM-46.exe /S
113 | #msiexec.exe /qn /norestart /i $tempdir\$puppetInstall PUPPET_MASTER_SERVER=$PROJECT_PREFIX-puppet-p.c.$PROJECT_NAME.internal PUPPET_AGENT_ENVIRONMENT=$PUPPET_AGENT_ENVIRONMENT /l* $LOG
114 | }else{
115 | Write-Host "Stackdriver is already installed"
116 | }
117 |
118 | Write-Host "Configuring windows-startup-script-url"
119 | $name = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/name
120 | $zone = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/zone
121 | $function = Invoke-RestMethod -Headers @{"Metadata-Flavor" = "Google"} -Uri http://169.254.169.254/computeMetadata/v1/instance/attributes/function
122 |
123 | if ($function -eq "pdc"){
124 | Write-Host "Setting windows-startup-script-url in metadata to $GcsPrefix/powershell/bootstrap/primary-domain-controller-step-1.ps1"
125 | gcloud compute instances add-metadata "$name" --zone $zone --metadata windows-startup-script-url="$GcsPrefix/powershell/bootstrap/primary-domain-controller-step-1.ps1"
126 | }elseif ($function -eq "sql"){
127 | Write-Host "Setting windows-startup-script-url in metadata to $GcsPrefix/powershell/bootstrap/install-sql-server-principal-step-1.ps1"
128 | gcloud compute instances add-metadata "$name" --zone $zone --metadata windows-startup-script-url="$GcsPrefix/powershell/bootstrap/domain-member.ps1"
129 | }
130 |
131 | Write-Host "Removing windows-startup-script-ps1 from metadata ..."
132 | gcloud compute instances remove-metadata "$name" --zone $zone --keys="windows-startup-script-ps1"
133 |
134 | Write-Host "Restarting computer after winstartup ..."
135 | Restart-Computer
136 |
--------------------------------------------------------------------------------